为什么大小为::<;MyStruct>;()不等于其字段大小之和?
我试图测量结构及其字段()的大小: 因此,结构的大小大于其字段大小之和(即为什么大小为::<;MyStruct>;()不等于其字段大小之和?,struct,rust,memory-alignment,memory-layout,Struct,Rust,Memory Alignment,Memory Layout,我试图测量结构及其字段()的大小: 因此,结构的大小大于其字段大小之和(即5)。为什么会这样?差异是由于为了满足a类型的要求。特定类型的值不希望存在于任意地址,而只存在于可被类型对齐整除的地址。例如,以char为例:它的对齐方式是4,因此它只希望生活在可以被4整除的地址上,比如0x4、0x8或0x7ffd463761bc,而不希望生活在0x6或0x7FFD4637611BD这样的地址上 类型的对齐依赖于平台,但通常情况下,大小为1、2或4的类型也分别具有1、2和4的对齐。对齐1意味着该类型的值在
5
)。为什么会这样?差异是由于为了满足a类型的要求。特定类型的值不希望存在于任意地址,而只存在于可被类型对齐整除的地址。例如,以char
为例:它的对齐方式是4
,因此它只希望生活在可以被4整除的地址上,比如0x4
、0x8
或0x7ffd463761bc
,而不希望生活在0x6
或0x7FFD4637611BD
这样的地址上
类型的对齐依赖于平台,但通常情况下,大小为1
、2
或4
的类型也分别具有1
、2
和4
的对齐。对齐1
意味着该类型的值在任何地址都感觉舒适(因为任何地址都可以被1
整除)
那么现在你的结构呢?生锈了
复合结构的对齐方式等于其场对齐方式的最大值
这意味着MyStruct
类型的对齐方式也是4
。我们可以通过和检查:
Oops,obj.bar
现在位于0x5
,尽管它的对齐方式是4!那太糟糕了
为了解决这个问题,Rust编译器将所谓的填充(未使用的字节)插入到结构中。在内存中,它现在看起来是这样的:
0x4: [obj.bar's first byte]
0x5: [obj.bar's second byte]
0x6: [obj.bar's third byte]
0x7: [obj.bar's fourth byte]
0x8: [obj.foo]
0x4: [[0].bar's first byte]
0x5: [[0].bar's second byte]
0x6: [[0].bar's third byte]
0x7: [[0].bar's fourth byte]
0x8: [[0].foo]
0x9: [[0]'s padding byte]
0xA: [[0]'s padding byte]
0xB: [[0]'s padding byte]
0xC: [[1].bar's first byte]
0xD: [[1].bar's second byte]
0xE: [[1].bar's third byte]
0xF: [[1].bar's fourth byte]
0x10: [[1].foo]
0x11: [[1]'s padding byte]
0x12: [[1]'s padding byte]
0x13: [[1]'s padding byte]
0x14: ...
0x4:[obj.foo]
0x5:填充(未使用)
0x6:填充(未使用)
0x7:填充(未使用)
0x8:[对象条的第一个字节]
0x9:[对象条的第二个字节]
0xA:[对象条的第三个字节]
0xB:[对象条的第四个字节]
由于这个原因,MyStruct
的大小是8,因为编译器添加了3个填充字节。现在一切又好了
。。。除了浪费的空间?事实上,这是不幸的。解决方案是交换结构的字段。幸运的是,在C++中,结构的内存布局未指定,这与C或C++不同。特别是,Rust编译器可以更改字段的顺序。您不能假定obj.foo
的地址低于obj.bar
由于Rust 1.18,此优化由编译器执行
但是,即使使用更新的或等于1.18的Rust编译器,结构的大小仍然是8字节。为什么? 内存布局还有另一条规则:结构的大小必须始终是其对齐方式的倍数。这对于在数组中密集布局这些结构非常有用。假设编译器将对结构字段重新排序,内存布局如下所示:
0x4: [obj.bar's first byte]
0x5: [obj.bar's second byte]
0x6: [obj.bar's third byte]
0x7: [obj.bar's fourth byte]
0x8: [obj.foo]
0x4: [[0].bar's first byte]
0x5: [[0].bar's second byte]
0x6: [[0].bar's third byte]
0x7: [[0].bar's fourth byte]
0x8: [[0].foo]
0x9: [[0]'s padding byte]
0xA: [[0]'s padding byte]
0xB: [[0]'s padding byte]
0xC: [[1].bar's first byte]
0xD: [[1].bar's second byte]
0xE: [[1].bar's third byte]
0xF: [[1].bar's fourth byte]
0x10: [[1].foo]
0x11: [[1]'s padding byte]
0x12: [[1]'s padding byte]
0x13: [[1]'s padding byte]
0x14: ...
看起来像是5个字节,对吗?不!想象一下有一个数组[MyStruct]
。在数组中,内存中的所有元素都相邻:
0x4: [[0].bar's first byte]
0x5: [[0].bar's second byte]
0x6: [[0].bar's third byte]
0x7: [[0].bar's fourth byte]
0x8: [[0].foo]
0x9: [[1].bar's first byte]
0xA: [[1].bar's second byte]
0xB: [[1].bar's third byte]
0xC: [[1].bar's fourth byte]
0xD: [[1].foo]
0xE: ...
哎呀,现在数组的第二个元素的条开始于0x9
!因此,实际上,数组大小需要是其对齐的倍数。因此,我们的记忆是这样的:
0x4: [obj.bar's first byte]
0x5: [obj.bar's second byte]
0x6: [obj.bar's third byte]
0x7: [obj.bar's fourth byte]
0x8: [obj.foo]
0x4: [[0].bar's first byte]
0x5: [[0].bar's second byte]
0x6: [[0].bar's third byte]
0x7: [[0].bar's fourth byte]
0x8: [[0].foo]
0x9: [[0]'s padding byte]
0xA: [[0]'s padding byte]
0xB: [[0]'s padding byte]
0xC: [[1].bar's first byte]
0xD: [[1].bar's second byte]
0xE: [[1].bar's third byte]
0xF: [[1].bar's fourth byte]
0x10: [[1].foo]
0x11: [[1]'s padding byte]
0x12: [[1]'s padding byte]
0x13: [[1]'s padding byte]
0x14: ...
相关的:
除了默认的#[repr(Rust)]
布局之外,还有其他可用选项
您可以使用#[repr(packed)]
使您的表示更加紧凑:
这将把所有字段对齐到最近的字节,而不管它们的首选对齐方式如何。因此,输出将是:
MyStruct:5
obj:5
obj.foo:1
对象栏:4
这可能不如默认的Rust表示,许多CPU根本不支持它,尤其是较旧的CPU或智能手机上的CPU。至少在某些现代CPU上的某些用例很少或没有性能损失(但您也应该阅读本文的评论,因为它们包含许多反例)。可能值得注意的是,尽管内存布局优化在理论上一直是可能的,它直到今年才真正实现(多亏了)。还值得注意的是,结构的大小始终是其对齐的倍数,因此按顺序排列的数组元素保持对齐。因此,即使将char放在第一位,将u8放在第二位,也仍然有填充。如果结构中有多个U8,则重新排列元素很有趣;编译器可以将它们组合在一起。