repr(Rust)

原文跟踪repr-rust.md   Commit: 7f019ec5c87da39fe0b9b5149e413d914528e945

首先,所有类型都以指定的字节数为单位进行对齐。类型的对齐单位确定了哪些地址能有效地存储值。 一个n字节对齐的值只能存储在一个n的整数倍地址上。 因此,2字节对齐意味着你必须存储在偶数地址上,1字节对齐表示你可以存放在任何地址上。对齐单位最小为1字节,通常是2的整数次幂。

原生类型的对齐单位通常与它们的大小一致,但是这也取决于特定的平台。 例如,在x86上,u64f64通常是4字节(32位)对齐的。

类型的大小必须始终是其对齐单位的整数倍。 这确保了该类型的数组可以始终通过偏移其大小的整数倍进行索引。 需要注意的是,对于动态大小的类型,其大小和对齐单位可能无法静态地确定。

Rust为您提供了以下几种复合类型进行数据布局(译者注:关于和类型积类型,可参考维基百科词条代数数据类型):

  • structs(命名的积类型)
  • tuples(匿名的积类型)
  • arrays(同类型的积类型)
  • enums (命名的和类型 - 带标签的unions
  • unions(无标签的unions

如果一个enum类型的所有成员都没有关联数据,则称这个enum是field-less的。

默认情况下,复合结构体的对齐单位等于它所有成员字段的对齐单位的最大值。因此,Rust会在必要时插入填充字段,以确保所有字段都能够正确地对齐,并且整个类型的大小是其对齐单位的整数倍。例如:

struct A { a: u8, b: u32, c: u16, }

将在目标机器上进行32位对齐,将这些原生类型进行以合适大小进行对齐。因此整个结构体的大小是32位的倍数。它可能会变成:

struct A { a: u8, _pad1: [u8; 3], // to align `b` b: u32, c: u16, _pad2: [u8; 2], // to make overall size multiple of 4 }

或者可能变成:

struct A { b: u32, c: u16, a: u8, _pad: u8, }

毫无意外,所有数据都存储在结构中,正如您在C中所期望的那样。但是,除了数组(按顺序紧凑排列)之外,默认情况下,Rust没有指定数据结构的内存布局。给出以下两个结构体定义:

struct A { a: i32, b: u64, } struct B { a: i32, b: u64, }

Rust 保证 A的两个实例会以完全相同的方式布局它们的数据。但目前而言,Rust 不保证A的实例与B的实例具有相同的字段排序或填充字段。

就上述的A和B而言,这一点似乎很迂腐,但 Rust 的其他几个特性使得语言可以以复杂的方式进行内存布局。

例如,考虑这个多态结构体:

struct Foo<T, U> { count: u16, data1: T, data2: U, }

再考虑单态化的Foo<u32, u16>Foo<u16, u32>。如果Rust以指定的顺序排列字段,我们希望它会填充结构体中的值以满足其对齐要求。因此,如果Rust没有对字段进行重排的话,我们希望它产生以下布局:

struct Foo<u16, u32> { count: u16, data1: u16, data2: u32, } struct Foo<u32, u16> { count: u16, _pad1: u16, data1: u32, data2: u16, _pad2: u16, }

后一种情况显然很浪费空间。内存占用的优化需要不同的单态化实例具有不同成员字段排序。

枚举体使这一考虑变得更加复杂,如:

enum Foo { A(u32), B(u64), C(u8), }

可能被布局为:

struct FooRepr { data: u64, // this is either a u64, u32, or u8 based on `tag` tag: u8, // 0 = A, 1 = B, 2 = C }

事实上,它大体就是这样进行布局的(以枚举成员的大小和位置为标签tag )。

然而,在某些情况下,这种布局的内存使用率会比较低下。经典的情况是 Rust 的“空指针优化”:由单个外部单元变量(例如None)和(可能嵌套的)非可空指针变量(例如Some(&T))组成的枚举使得标签没有存在的必要。空指针可以被安全地解释为单一的(None)变量。最终结果是,例如,size_of::<Option<&T>>() == size_of::<&T>()

Rust中有许多类型是(或包含)非可空指针,如Box<T>Vec<T>String&T&mut T。同样,可以想象得到,嵌套的枚举体将其标签汇集到单个判别式中,因为根据定义,它们的有效值的范围是有限的。对于枚举,原则上可以使用相当精细的位运算算法来存储嵌套类型中的无效值。因此,特别地,我们现在没有指定枚举的布局方式。