我尝试测量结构及其字段(Playground)的大小:
use std::mem;
struct MyStruct {
foo: u8,
bar: char,
}
println!("MyStruct: {}", mem::size_of::<MyStruct>());
let obj = MyStruct { foo: 0, bar: '0' };
println!("obj: {}", mem::size_of_val(&obj));
println!("obj.foo: {}", mem::size_of_val(&obj.foo));
println!("obj.bar: {}", mem::size_of_val(&obj.bar));
此程序打印:
MyStruct: 8
obj: 8
obj.foo: 1
obj.bar: 4
因此结构的大小大于其字段大小的总和(这将是5
)。那是为什么?
答案 0 :(得分:5)
差异是由padding引起的,以满足类型alignment的要求。特定类型的值不希望生活在任意地址,但仅限于可被类型整除的地址。 对准 即可。例如,取char
:它的对齐方为4
,因此它只想生活在可被4整除的地址,例如0x4
,0x8
或0x7ffd463761bc
,而不是0x6
或0x7ffd463761bd
等地址。
类型的对齐取决于平台,但通常情况下,1
,2
或4
大小的对齐方式为1
,分别是2
和4
。 1
的对齐意味着该类型的值在任何地址都感觉舒适(因为任何地址都可以被1
整除)。
那么你的结构呢?在Rust,
复合结构的对齐等于其字段的最大值。对准。
这意味着您的MyStruct
类型的对齐方式也是4
。我们可以使用mem::align_of()
和mem::align_of_val()
检查:
// prints "4"
println!("{}", mem::align_of::<MyStruct>());
现在假设你的结构的值位于0x4
(满足结构的直接对齐要求):
0x4: [obj.foo]
0x5: [obj.bar's first byte]
0x6: [obj.bar's second byte]
0x7: [obj.bar's third byte]
0x8: [obj.bar's fourth byte]
哎呀,obj.bar
现在住在0x5
,虽然它的排列是4!那很糟糕!
为了解决这个问题,Rust编译器将所谓的 padding - 未使用的字节 - 插入到struct中。在内存中,它现在看起来像这样:
0x4: [obj.foo]
0x5: padding (unused)
0x6: padding (unused)
0x7: padding (unused)
0x8: [obj.bar's first byte]
0x9: [obj.bar's second byte]
0xA: [obj.bar's third byte]
0xB: [obj.bar's fourth byte]
因此,MyStruct
的大小为8,因为编译器添加了3个填充字节。现在一切都很好!
......除了浪费的空间?实际上,这是不幸的。解决方案是交换struct的字段。幸运的是,为了这个目的,Rust中的结构的内存布局是未指定的,与C或C ++不同。特别是,允许Rust编译器更改字段的顺序。您不能认为obj.foo
的地址低于obj.bar
!
由于 Rust 1.18 ,此优化由编译器执行。
但即使Rust编译器更新或等于1.18,您的结构仍然是8字节大小。为什么呢?
内存布局还有另一个规则:结构的大小必须始终是其对齐的倍数。这对于能够在阵列中密集布局这些结构非常有用。假设编译器将重新排序我们的struct字段,内存布局如下所示:
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]
看起来像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: ...
哎呀,现在阵列的第二个元素bar
从0x9
开始!实际上,数组大小需要是其对齐的倍数。因此,我们的记忆如下:
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: ...
相关:
答案 1 :(得分:3)
除了默认的packet MyPacket
{ string msg;
int srcAddress;
int destAddress;
};
布局外,还有其他可用选项,as explained in the Rustonomicon。
您可以使用#[repr(Rust)]
:
#[repr(packed)]
这将使所有字段与最近的字节对齐,而不管它们的首选对齐方式如何。所以输出将是:
#[repr(packed)]
struct MyStruct {
foo: u8,
bar: char,
}
这可能不如默认的Rust表示高,并且许多CPU根本不支持它,特别是旧CPU或智能手机上的CPU。 evidence至少某些现代CPU上的某些用例几乎没有性能损失(但您还应该阅读文章&# 39;评论,因为它们包含许多反例)。