为什么一个 uint64_t 在类中使用时需要比 2 个 uint32_t 更多的内存?
原因是由于对齐要求的填充。
在大多数 64 位架构上,uint8_t 的对齐要求为 1,uint16_t 的对齐要求为 2,uint32_t 的对齐要求为 4,uint64_t 的对齐要求为 8。编译器必须确保结构中的所有成员都正确对齐并且结构的大小是其整体对齐要求的倍数。此外,编译器不允许重新排序成员。
所以你的结构最终布局如下
struct class1
{
uint8_t a; //offset 0
uint8_t b; //offset 1
uint16_t c; //offset 2
uint32_t d; //offset 4
uint32_t e; //offset 8
uint32_t f; //offset 12
uint32_t g; //offset 16
}; //overall alignment requirement 4, overall size 20.
struct class2
{
uint8_t a; //offset 0
uint8_t b; //offset 1
uint16_t c; //offset 2
uint32_t d; //offset 4
uint32_t e; //offset 8
// 4 bytes of padding because f has an alignment requirement of 8
uint64_t f; //offset 16
}; //overall alignment requirement 8, overall size 24
以及如何防止这种情况?
不幸的是,没有好的通用解决方案。
有时可以通过重新排序字段来减少填充量,但这对您的情况没有帮助。它只是在结构中移动填充。具有需要 8 字节对齐的字段的结构的大小始终是 8 的倍数。因此,无论您如何重新排列字段,您的结构始终具有至少 24 的大小。
您可以使用编译器特定的功能,例如#pragma pack
或__attribute((packed))
强制编译器将结构打包得比正常对齐要求所允许的更紧密。但是,除了限制可移植性之外,这在获取成员的地址或绑定对成员的引用时会产生问题。生成的指针或引用可能不满足对齐要求,因此使用起来可能不安全。
不同的编译器处理这个问题的方式不同。来自一些在上帝螺栓上玩耍的人。
- g++ 9 到 11 将拒绝绑定对打包成员的引用,并在获取地址时发出警告。
- clang 4 到 11 将在获取地址时发出警告,但会默默地绑定一个引用并将该引用传递到编译单元边界。
- Clang 3.9 及更早版本将获取地址并静默绑定引用。
- g++ 8 及更早版本和 clang 3.9 及更早版本(直到 Godbolt 上的最旧版本)也将拒绝绑定引用,但会在没有警告的情况下获取地址。
- icc 将绑定一个指针或获取地址,而不会在任何一种情况下产生任何警告(尽管公平地说,英特尔处理器支持硬件中的未对齐访问)。