assert_eq!(12, mem::size_of::<(i32, f64)>()); // failed
assert_eq!(16, mem::size_of::<(i32, f64)>()); // succeed
assert_eq!(16, mem::size_of::<(i32, f64, i32)>()); // succeed
为什么不是12(4+8)?
assert_eq!(12, mem::size_of::<(i32, f64)>()); // failed
assert_eq!(16, mem::size_of::<(i32, f64)>()); // succeed
assert_eq!(16, mem::size_of::<(i32, f64, i32)>()); // succeed
为什么不是12(4+8)?
为什么不是12(4+8)?Rust对元组有特殊处理吗?
不.一个常规 struct 可能(而且确实)有同样的"问题".
答案是padding:在64位系统上,f64
应该与8字节对齐(也就是说,它的起始地址应该是8的倍数).一个 struct 通常会对齐其最受约束(最大对齐)的成员,因此元组的对齐度为8.
这意味着元组必须从8
的倍数开始,因此i32
从8的倍数开始,以4的倍数结束(因为它是4字节),编译器添加4字节的填充,以便f64
正确对齐:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
[ i32 ] padding [ f64 ]
"但是等等,"你喊道,"如果我反转元组的字段,大小不会改变!".
这是真的:上面的模式并不准确,因为默认情况下,rustc
会将字段重新排序为压缩 struct ,所以它会执行以下操作:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
[ f64 ] [ i32 ] padding
这就是为什么你的第三次try 是16字节:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
[ f64 ] [ i32 ] [ i32 ]
而不是24小时:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
[ 32 ] padding [ f64 ] [ 32 ] padding
"稳住你的马,"你说,目光敏锐地看着自己,"我能看到f64的路由,但为什么在末端有填充物?那里没有f64!"
这就是为什么计算机更容易处理序列:a struct with a given alignment should also have a size that's a multiple of its alignment,如果你有多个序列:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
[ f64 ] [ i32 ] padding [ f64 ] [ i32 ] padding
它们正确地对齐了and.如何放置下一个的计算很简单(只需通过 struct 的大小进行偏移),这也避免了将这些信息放入everywhere.基本上,数组/vec本身从不填充,而是填充在它存储的 struct 中.这允许packing成为 struct 属性,并且不会影响array.
使用repr(C)
属性,你可以告诉Rust按照你给出的顺序放置你的 struct (这不是元组FWIW的选项).
这是安全的,虽然它不是usually有用,但有一些边缘情况很重要,我知道的(可能还有其他情况)是:
你也可以用repr(packed)
来分辨rustc
到not pad the structure.
这是更高的风险,它通常会降低性能(大多数CPU与未对齐的数据交叉),并在某些体系 struct 上降低性能.这在很大程度上取决于CPU体系 struct 和在其上运行的系统(OS):每the kernel's Unaligned Memory Accesses document个
因此,"1类"架构将执行正确的访问,可能会以性能为代价.
"2类"体系 struct 将以高性能成本执行正确的访问(CPU需要调用操作系统,未对齐的访问将转换为对齐的访问in software),假设操作系统能够处理这种情况(在这种情况下,这并不总是解决为3类体系 struct ).
"3类"体系 struct 将在未对齐的访问上终止程序(因为系统无法修复它).
"Class 4"将对未对齐的访问执行无意义操作,是迄今为止最糟糕的.
另一个常见的trap 或未对齐的访问是,它们往往是非原子的(因为它们需要扩展为一系列对齐的内存操作和操作),因此即使对于其他原子访问,您也可能会被"撕裂"读取或写入.