c - 防止 GCC 优化掉对内存映射地址的循环写入

标签 c for-loop gcc volatile 68000

我有一个指向像这样的控制端口的地址(对于上下文我正在开发 Sega/Megadrive 游戏):

volatile u32 * vdp_ctrl = (u32 *) 0x00C00004;

以及我想设置的一组初始值:
const u8 initial_vdp_vals[24] = {
  0x20,
  0x74,
  0x30,
  ..etc
};

有一个循环:
typedef struct {
  u16 upper;
  u8 reg;
  u8 val;
} bitset;

typedef union {
  bitset b;
  u32 as_u32;
} u_bitset;

static inline void init_vdp() {
  u_bitset cmd = {{0x00008000}};
  for(int i = 0; i < 24; i++) {
     cmd.b.val = initial_vdp_vals[i];
     *vdp_ctrl = cmd.as_u32;
     cmd.b.reg += 1;
  }
}

问题是 gcc(至少使用 O2)优化了它并且只将最后一个值写入 *vdp_ctrl指针。我设法通过设置 bitset 中的一个属性来解决这个问题。结构到 volatile ,但这似乎不是一个直观的解决方案,因此它生成的程序集非常冗长。

所以我的问题有两个:
  • 向 gcc 建议我的 vdp_ctrl 的“正确”方式是什么?随着时间的推移,指针需要接受多次写入。我将经常使用这种模式(将常量/静态数据写入循环中的控制/数据地址),并将随机字段标记为 volatile 似乎不直观。
  • 我用于生成的 asm 的“质量标准”如下所示(不是 gcc 生成的):
  •     move.l #initial_vdp_vals, a0
        move.l #24, d0
        move.l #0x00008000, d1
    
    .copy:
        move.b (a0)+, d1
        move.w d1, 0x00C00004
        add.w #0x0100, d1
        dbra d0, .copy
    

    这是非常好的和简洁的。所以我的另一个问题可能是(作为一个完整的 C 新手):我的 C 解决方案是否有更好的方法让我更接近上面的程序集?老实说,我什至不确定我的代码是否正确,因为我只是想先解决这个循环优化问题,因为我知道这将是一个持续不断的问题。

    产生我的问题的可运行示例:
    volatile unsigned long * vdp_ctrl = (unsigned long *) 0x00C00004;
    
    const unsigned char initial_vdp_vals[24] = {
      0x20,
      0x74,
      0x30,
      0x40,
      0x05,
      0x70,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00,
      0x08,
      0x81,
      0x34,
      0x00,
      0x00,
      0x01,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00
    };
    
    typedef struct {
      unsigned int upper;
      unsigned char reg;
      unsigned char val;
    } bitset;
    
    typedef union {
      bitset b;
      unsigned long as_u32;
    } u_bitset;
    
    static inline void init_vdp() {
      u_bitset cmd = {{0x00008000}};
      for(int i = 0; i < 24; i++) {
         cmd.b.val = initial_vdp_vals[i];
         *vdp_ctrl = cmd.as_u32;
      }
    }
    
    void init() {
      init_vdp();
      for(;;) {
      }
    }
    

    m68k-linux-gnu-gcc -ffreestanding -O2 -S -c test.c -o test.s .生成以下内容:
    #NO_APP
        .file   "test.c"
        .text
        .align  2
        .globl  init
        .type   init, @function
    init:
        link.w %fp,#0
        move.l vdp_ctrl,%a0
        moveq #24,%d0
        move.l #32768,%d1
    .L2:
        move.l %d1,(%a0)
        subq.l #1,%d0
        jne .L2
    .L3:
        jra .L3
        .size   init, .-init
        .globl  initial_vdp_vals
        .section    .rodata
        .type   initial_vdp_vals, @object
        .size   initial_vdp_vals, 24
    initial_vdp_vals:
        .byte   32
        .byte   116
        .byte   48
        .byte   64
        .byte   5
        .byte   112
        .byte   0
        .byte   0
        .byte   0
        .byte   0
        .byte   0
        .byte   8
        .byte   -127
        .byte   52
        .byte   0
        .byte   0
        .byte   1
        .byte   0
        .byte   0
        .byte   0
        .byte   0
        .byte   0
        .byte   0
        .byte   0
        .globl  vdp_ctrl
        .data
        .align  2
        .type   vdp_ctrl, @object
        .size   vdp_ctrl, 4
    vdp_ctrl:
        .long   12582916
        .ident  "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
        .section    .note.GNU-stack,"",@progbits
    
    gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
    

    备注 :看来我的数组的大小决定了它是否得到优化。当我只制作两个元素时,它没有优化。

    最佳答案

    在这种代码中,您应该使用精确大小的整数。我强烈建议打包结构和 union 。

    #include <stdint.h>
    #define vdp_ctrl  ((volatile uint32_t *) 0x00C00004)
    
    const unsigned char initial_vdp_vals[24] = {
      0x20,
      0x74,
      0x30,
      0x40,
      0x05,
      0x70,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00,
      0x08,
      0x81,
      0x34,
      0x00,
      0x00,
      0x01,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00
    };
    
    typedef struct {
      uint16_t upper;
      uint8_t reg;
      uint8_t val;
    } __attribute__((packed)) bitset;
    
    typedef union {
      bitset b;
      uint32_t as_u32;
    } __attribute__((packed)) u_bitset ;
    
    static inline void init_vdp() {
      u_bitset cmd = {.b.upper = 0x00008000};
      for(int i = 0; i < 24; i++) 
      {
         cmd.b.val = initial_vdp_vals[i];
         *vdp_ctrl = cmd.as_u32;
      }
    }
    
    void init() {
      init_vdp();
      for(;;) {
      }
    }
    

    它会生成您需要的代码。

    IMO 最好有宏而不是真实对象。在这个琐碎的代码中它可能没有任何区别,但如果代码变得更复杂,它会产生任何区别。

    关于c - 防止 GCC 优化掉对内存映射地址的循环写入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60459889/

    相关文章:

    c++ - Mac OS 10.5.8 Leopard 支持的 Boost C++ 库?

    c - 如何定义全局 gsl_vector

    c++ - for(;i<=m;i++) 是什么意思?

    python - 当项目已存在以及使用 for 循环时将其添加到列表中

    c - for循环永远不会在c中结束

    c++ - Trec_eval 使用 cygwin 出错

    c++ - 从文件中读取大量数据并以有效的方式解析日期。如何提高海量数据的性能?

    c - fork 和信号 : how to send signals from parent process to specific child process

    javascript - jQuery el.each() 返回 HTMLAnchorElement 而不是元素实例

    c - 访问 int (*foo)[3] 的索引 4 时没有警告