rust - 为什么元组或结构的大小不是成员的总和?

标签 rust memory-layout

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)?
Rust 对元组有特殊处理吗?

最佳答案

Why is it not 12 (4 + 8)? Does Rust have special treatment for tuples?


不。常规结构可以(并且确实)具有相同的“问题”。
答案是padding :在 64 位系统上,一个 f64应该对齐到 8 个字节(即它的起始地址应该是 8 的倍数)。结构通常具有其最受约束(最大对齐)成员的对齐方式,因此元组的对齐方式为 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会将您的字段重新排序为紧凑的结构,因此它确实会这样做:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
[     f64     ] [ i32 ] padding 
这就是为什么您的第三次尝试是 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!”
好吧,这样计算机就可以更轻松地处理序列:具有给定对齐方式的结构也应该具有与其对齐方式的倍数的大小,这样当您有多个它们时:
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 
它们正确对齐并且如何放置下一个的计算很简单(只是被结构的大小抵消),它还避免将这些信息放在任何地方。基本上,数组/vec 本身永远不会被填充,而是填充在它存储的结构中。这允许打包成为结构属性,并且也不会感染数组。

使用 repr(C) 属性,你可以告诉 Rust 完全按照你给出的顺序放置你的结构(这不是元组 FWIW 的选项)。
这是安全的,虽然它通常没有用,但在某些边缘情况下它很重要,我知道的那些(可能还有其他)是:
  • 与外来 (FFI) 代码接口(interface),它需要一个非常具体的布局,这实际上是标志名称的来源(它使 Rust 表现得像 C)。
  • 避免 false sharing在高性能代码中。

  • 您也可以告诉rustc不使用 repr(packed) 填充结构.
    那风险更大,它通常会降低性能(大多数 CPU 与未对齐的数据交叉)并且可能会导致程序崩溃或在某些架构上完全返回错误的数据。这高度依赖于 CPU 架构,以及在其上运行的系统 (OS):每 the kernel's Unaligned Memory Accesses document
  • 某些架构能够执行未对齐的内存访问
    透明,但通常会有显着的性能成本。
  • 某些架构在未对齐访问时引发处理器异常
    发生。异常处理程序能够纠正未对齐的访问,
    以显着的性能成本为代价。
  • 某些架构在未对齐访问时引发处理器异常
    发生,但异常不包含足够的信息
    要纠正未对齐的访问。
  • 某些架构无法进行未对齐的内存访问,但可以
    默默地对请求的内存执行不同的内存访问,
    导致难以检测的微妙代码错误!

  • 因此,“1 类”架构将执行正确的访问,但可能会以性能为代价。
    “第 2 类”架构将以较高的性能成本执行正确的访问(CPU 需要调用操作系统,并且未对齐的访问在软件中转换为对齐的访问),假设操作系统处理这种情况(它不t 总是在这种情况下,这将解析为 3 类架构)。
    “第 3 类”架构将在未对齐访问时终止程序(因为系统无法修复它。
    “Class 4”将对未对齐的访问执行无意义的操作,并且是迄今为止最糟糕的。
    另一个常见的陷阱或未对齐的访问是它们往往是非原子的(因为它们需要扩展为一系列对齐的内存操作和对这些操作的操作),因此即使对于其他原子访问,您也可能会“撕裂”读取或写入.

    关于rust - 为什么元组或结构的大小不是成员的总和?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65707981/

    相关文章:

    c++ - 当基类不是多态但派生时 'this' 地址不匹配

    Rust tokio 无限循环用于多个按钮监听

    linked-list - 没有 malloc 的 no_std 中的无堆链表

    c++ - 如何在类(class)结束时获得填充的大小

    c++ - 虚函数和多重继承情况下的对象布局

    swift - 在 Swift 3 中从数据初始化结构

    linux - 如何在 EC2 中的 RHEL 上安装 Rust 工具集?

    xml - bsdtar文件分隔符字节?

    generics - 为什么我会收到错误 "Expected type parameter, found integral variable"?

    c++ - 全局变量的内存布局