使用struct成员作为内存偏移的引用是否安全?
在ARM信息中心站点上,他们有使用结构将变量映射到内存地址的建议。
#define PORTBASE 0x40000000 typedef struct Port { uint32_t reg0; uint32_t reg1; uint32_t reg2; } Port; volatile struct Port * const reg_p = (struct Port *)PORTBASE;
但是,我看到其他人建议编译器可以在struct对象的成员之间添加填充,并且确保不会发生这种情况的唯一方法是使用packed属性,例如在GCC __attribute__((__packed__))
。
在我看来,填充只会由编译器引入以对齐成员边界,但我没有在C99标准中看到它明确指出这不应该在其他情况下发生。 事实上,似乎它说可能会发生。
从C99第6.7.2.1节开始,
12
结构或联合对象的每个非位字段成员以适合其类型的实现定义方式对齐。
13
在结构对象中,非位字段成员和位字所在的单元具有按声明顺序增加的地址。 指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 结构对象中可能存在未命名的填充,但不是在其开头。
15
结构或联合的末尾可能有未命名的填充。
鉴于上面的例子,是否保证reg1与reg0完全相差32位而不告诉编译器不添加填充?
添加成员之间的填充,以便将值与32Bit对齐(取决于体系结构),例如,uint32_t应该从对齐的偏移量4 * 2 ^ x开始,以便更快地访问,因为可以使用正常的32位指针如果PORTBASE是一个Aligned地址,则它只包含32Bit值,这是自动为真的。
因此,编译器在这种情况下应该不添加填充,但您可以随时添加
__attribute__((__packed__))
为了确定。
编译器会在以下情况下添加填充:
struct { uint8_t a; uint32_t b; }
其中b将以未对齐的地址结束。
问题是,对于arm而言,您可能会得到不完全兼容C99的编译器。