我有一个指向像这样的控制端口的地址(对于上下文我正在开发 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
,但这似乎不是一个直观的解决方案,因此它生成的程序集非常冗长。所以我的问题有两个:
vdp_ctrl
的“正确”方式是什么?随着时间的推移,指针需要接受多次写入。我将经常使用这种模式(将常量/静态数据写入循环中的控制/数据地址),并将随机字段标记为 volatile 似乎不直观。 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/