我了解结构成员之间发生的填充,以确保各个类型的正确对齐。但是,为什么数据结构必须是最大成员对齐的倍数?我不明白最后需要填充。
4 回答
好问题。考虑这种假设类型:
struct A {
int n;
bool flag;
};
因此,一个类型的对象A
应该占用 5 个字节(4 个用于 int 加上 1 个用于 bool),但实际上它占用了 8 个字节。为什么?
如果您使用这样的类型,就会看到答案:
const size_t N = 100;
A a[N];
如果每个A
只有五个字节,那么a[0]
将对齐 but a[1]
,a[2]
而大多数其他元素不会。
但为什么对齐甚至很重要?有几个原因,都与硬件有关。原因之一是最近/经常使用的内存被缓存在CPU 芯片上的缓存行中,以便快速访问。小于缓存行的对齐对象总是适合单行(但请参阅下面附加的有趣注释),但未对齐对象可能跨越两行,浪费缓存。
实际上还有更基本的硬件原因,与字节寻址数据沿 32 位或 64 位数据总线传输的方式有关,与高速缓存线完全不同。不对齐不仅会因额外的取指而阻塞总线(与以前一样由于跨接),而且还会迫使寄存器在字节进入时对其进行移位。更糟糕的是,不对齐往往会混淆优化逻辑(至少,英特尔的优化手册说确实如此,尽管我对最后一点没有个人了解)。因此,从性能的角度来看,错位是非常糟糕的。
由于这些原因,浪费填充字节通常是值得的。
更新: 下面的评论都很有用。我推荐他们。
根据硬件的不同,对齐可能是必要的,或者只是有助于加快执行速度。
在一定数量的处理器(我相信是 ARM)中,未对齐的访问会导致硬件异常。干净利落。
尽管典型的 x86 处理器更宽松,但访问未对齐的基本类型仍然会受到惩罚,因为处理器必须做更多的工作才能将位带入寄存器,然后才能对其进行操作。尽管如此,当需要打包时,编译器通常会提供特定的属性/编译指示。
因为虚拟寻址。
“......在页面大小的边界上对齐页面可以让硬件通过替换地址中的高位来将虚拟地址映射到物理地址,而不是进行复杂的算术运算。”
顺便说一句,我发现这个维基百科页面写得很好。
如果 CPU 的寄存器大小是 32 位,那么它可以通过一条汇编指令来抓取位于 32 位边界上的内存。抓取 32 位的速度较慢,然后获取从第 8 位开始的字节。
顺便说一句:不必有填充。您可以要求包装结构。