为什么size_of :: <MyStruct>()不等于其字段大小的总和?

Luk*_*odt 3 struct memory-alignment memory-layout rust

我试图测量结构及其字段(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));
Run Code Online (Sandbox Code Playgroud)

该程序打印:

MyStruct: 8
obj:      8
obj.foo:  1
obj.bar:  4
Run Code Online (Sandbox Code Playgroud)

因此结构的大小大于其字段大小的总和(这将是5).这是为什么?

Luk*_*odt 5

差异是由于填充以满足类型对齐要求.特定类型的值不希望存在于任意地址,而是仅存在于可被类型对齐整除的地址处.例如,取char:它有一个对齐,4所以它只想生活在可被4整除的地址,比如0x4,0x8或者0x7ffd463761bc,而不是像0x6或那样的地址0x7ffd463761bd.

一种类型的排列是与平台相关,但类型大小的它通常是真实的1,24有一个比对1,24分别了.对齐1意味着该类型的值在任何地址都感觉舒适(因为任何地址都可被整除1).

那么现在你的结构怎么样?在Rust,

复合结构的对齐方式等于其字段对齐的最大值.

这意味着您的MyStruct类型的对齐也是4.我们可以检查与mem::align_of()mem::align_of_val():

// prints "4"
println!("{}", mem::align_of::<MyStruct>());
Run Code Online (Sandbox Code Playgroud)

现在假设你的struct的值存在0x4(满足struct的直接对齐要求):

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]
Run Code Online (Sandbox Code Playgroud)

哎呀,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]
Run Code Online (Sandbox Code Playgroud)

出于这个原因,大小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]
Run Code Online (Sandbox Code Playgroud)

看起来像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:   ...
Run Code Online (Sandbox Code Playgroud)

哎呀,现在阵列的第二个元素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:  ...
Run Code Online (Sandbox Code Playgroud)

相关: