我创建了三个几乎相同的枚举:
#[derive(Clone, Debug)]
pub enum Smoller {
Int(u8),
Four([u8; 4]),
Eight([u8; 8]),
Twelve([u8; 12]),
Sixteen([u8; 16]),
}
#[derive(Clone, Debug)]
pub enum Smol {
Float(f32),
Four([u8; 4]),
Eight([u8; 8]),
Twelve([u8; 12]),
Sixteen([u8; 16]),
}
#[derive(Clone, Debug)]
pub enum Big {
Float(f64),
Four([u8; 4]),
Eight([u8; 8]),
Twelve([u8; 12]),
Sixteen([u8; 16]),
}
pub fn main() {
println!("Smoller: {}", std::mem::size_of::<Smoller>()); // => Smoller: 17
println!("Smol: {}", std::mem::size_of::<Smol>()); // => Smol: 20
println!("Big: {}", std::mem::size_of::<Big>()); // => Big: 24
}考虑到我对计算机和内存的理解,我所期望的是,它们应该是相同大小的。最大的变体是大小为16的[u8; 16]。因此,虽然这些枚举有不同的大小第一变体,但它们的最大变体的大小和变体总数相同。
我知道,当某些类型有空白时,Rust可以做一些优化来确认(例如,指针可能会崩溃,因为我们知道它们将无效,而0),但这实际上正好相反。我想如果我手工构造这个枚举,我可以把它放入17个字节(仅需要一个字节来区分),所以这20个字节和24个字节都让我感到困惑。
我怀疑这可能与对齐有关,但我不知道为什么,也不知道为什么需要这样做。
有人能解释一下吗?
谢谢!
发布于 2019-07-29 11:18:22
正如姆卡尔顿所提到的,这是内部字段对齐和对齐/大小规则的影响。
对齐
具体来说,内置类型的通用对齐方式是:
请注意,我说的是常见的,在实践中,对齐是由硬件决定的,在32位架构上,您可以合理地期望f64是4字节对齐的。此外,isize、usize和指针的对齐将根据32位和64位的体系结构而有所不同。
一般说来,为了便于使用,复合类型的对齐是其任何字段中最大的对齐方式。
对未对齐值的访问通常是特定于体系结构的;在某些体系结构上,它会崩溃(SIGBUS)或返回错误的数据,在某些结构上,访问速度会慢一些(就在不久前,x86/x64),而在另一些结构上,它可能只是很好(更新的x64,在某些指令上)。
尺寸和对齐
在C中,大小必须是对齐的倍数,因为数组的布局和迭代方式如下:
sizeof(T)字节来完成的。铁锈继承了这种行为^1。
值得注意的是,Swift决定定义一个单独的内部元素strideof来表示数组中的步长,这使得它们可以从sizeof的结果中删除任何尾部填充。它确实引起了一些混乱,正如人们所期望的那样,sizeof的行为像C,但允许更有效地压缩内存。
因此,在Swift中,您的吊床可以表示为:
Smoller:[u8 x 16][discriminant] =>大小为17字节,跨度为17字节,对齐为1字节。Smol:[u8 x 16][discriminant] =>大小为17字节,跨度为20字节,对齐为4字节。Big:[u8 x 16][discriminant] =>大小为17字节,跨24字节,对齐8字节。它清楚地显示了尺寸和步幅之间的差异,这是用C和Rust混合起来的。
^1我似乎还记得一些关于可能切换到strideof__的讨论,正如我们所看到的那样,这种讨论并没有取得成果,但却找不到与它们的联系。
发布于 2019-07-29 09:51:02
大小必须至少为17个字节,因为它的最大变体有16个字节大,并且它需要一个额外的字节来进行鉴别(在某些情况下编译器可以很聪明,并将鉴别器放在未使用的变体位中,但它不能在这里这样做)。
此外,Big的大小必须是对齐 f64的8个字节的倍数。大于17的8的较小倍数是24。同样,Smol不能只有17个字节,因为它的大小必须是4个字节的倍数(f32的大小)。Smoller只包含u8,因此它可以对齐到1字节。
发布于 2019-07-29 09:55:20
我认为这是因为内在价值观的一致性要求。
u8的对齐方式是1,所以所有这些都可以像您预期的那样工作,并且整个大小为17个字节。
但是f32有一个4对齐(从技术上讲,它依赖于拱形,但这是最有可能的值)。因此,即使鉴别值仅为1字节,也可以得到Smol::Float的布局。
[discriminant x 1] [padding x 3] [f32 x 4] = 8 bytes然后是Smol::Sixteen
[discriminant x 1] [u8 x 16] [padding x 3] = 20 bytes为什么这个垫子真的有必要?因为它要求类型的大小必须是对齐的倍数,否则这种类型的数组将不对齐。
类似地,f64的对齐方式是8,所以您得到了24的完整大小,这是最小的8倍数,适合所有枚举。
https://stackoverflow.com/questions/57250993
复制相似问题