repr(Rust)
原文跟踪repr-rust.md Commit: 7f019ec5c87da39fe0b9b5149e413d914528e945
首先,所有类型都以指定的字节数为单位进行对齐。类型的对齐单位确定了哪些地址能有效地存储值。 一个n
字节对齐的值只能存储在一个n
的整数倍地址上。 因此,2字节对齐意味着你必须存储在偶数地址上,1字节对齐表示你可以存放在任何地址上。对齐单位最小为1字节,通常是2的整数次幂。
原生类型的对齐单位通常与它们的大小一致,但是这也取决于特定的平台。 例如,在x86上,u64
和f64
通常是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
。同样,可以想象得到,嵌套的枚举体将其标签汇集到单个判别式中,因为根据定义,它们的有效值的范围是有限的。对于枚举,原则上可以使用相当精细的位运算算法来存储嵌套类型中的无效值。因此,特别地,我们现在没有指定枚举的布局方式。