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有用,但有一些边缘情况很重要,我知道的(可能还有其他情况)是:

  • 与 foreign (FFI)代码接口,这需要一个非常具体的布局,这实际上是旗帜名称的来源(它使Rust的行为类似于C).
  • 在高性能代码中避免false sharing.

你也可以用repr(packed)来分辨rustcnot pad the structure.

这是更高的风险,它通常会降低性能(大多数CPU与未对齐的数据交叉),并在某些体系 struct 上降低性能.这在很大程度上取决于CPU体系 struct 和在其上运行的系统(OS):每the kernel's Unaligned Memory Accesses document

  1. 有些体系 struct 能够执行未对齐的内存访问
  2. 某些体系 struct 在未对齐的访问时引发处理器异常
  3. 某些体系 struct 在未对齐的访问时引发处理器异常
  4. 有些体系 struct 不能进行未对齐的内存访问,但会

因此,"1类"架构将执行正确的访问,可能会以性能为代价.

"2类"体系 struct 将以高性能成本执行正确的访问(CPU需要调用操作系统,未对齐的访问将转换为对齐的访问in software),假设操作系统能够处理这种情况(在这种情况下,这并不总是解决为3类体系 struct ).

"3类"体系 struct 将在未对齐的访问上终止程序(因为系统无法修复它).

"Class 4"将对未对齐的访问执行无意义操作,是迄今为止最糟糕的.

另一个常见的trap 或未对齐的访问是,它们往往是非原子的(因为它们需要扩展为一系列对齐的内存操作和操作),因此即使对于其他原子访问,您也可能会被"撕裂"读取或写入.

Rust相关问答推荐

是否有一种可靠的方法可以将Arc弦乐转换/转换为Arc Vec u8>>

在不重写/专门化整个函数的情况下添加单个匹配手臂到特征的方法?

MPSC频道在接收器处阻塞

原始数组数据类型的默认trait实现

如何计算迭代器适配器链中过滤的元素的数量

自定义结果枚举如何支持`?`/`FromResidual`?

Gtk4-rs:将监视器作为gdk::monitor获取,而不是作为glib::对象获取

什么时候和为什么S最好是按值或引用传递简单类型

如何从ruust中的fig.toml中读取?

变量需要parse()中的显式类型

在Rust中,Box:ed struct 与普通 struct 在删除顺序上有区别吗?

在 Rust 中,是否可以定义一个需要实现类型的构造函数的对象安全特征?

将泛型中的 Box 转换为 rust 中的 Box

将 &str 或 String 保存在变量中

更新 rust ndarray 中矩阵的一行

闭包返回类型的生命周期规范

Rust 中 Mutex<> 的深拷贝?

为什么不能在 Rust 中声明静态或常量 std::path::Path 对象?

为什么我不能克隆可克隆构造函数的Vec?

Iterator::collect如何进行转换?