首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么使一个枚举变体‘f64’增加这个枚举的大小?

为什么使一个枚举变体‘f64’增加这个枚举的大小?
EN

Stack Overflow用户
提问于 2019-07-29 09:36:24
回答 3查看 574关注 0票数 4

我创建了三个几乎相同的枚举:

代码语言:javascript
复制
#[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个字节都让我感到困惑。

我怀疑这可能与对齐有关,但我不知道为什么,也不知道为什么需要这样做。

有人能解释一下吗?

谢谢!

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-07-29 11:18:22

正如姆卡尔顿所提到的,这是内部字段对齐和对齐/大小规则的影响。

对齐

具体来说,内置类型的通用对齐方式是:

  • 1: i8,u8.
  • 2: i16,u16.
  • 4: i32,u32,f32。
  • 8: i64,u64,f64。

请注意,我说的是常见的,在实践中,对齐是由硬件决定的,在32位架构上,您可以合理地期望f64是4字节对齐的。此外,isizeusize和指针的对齐将根据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__的讨论,正如我们所看到的那样,这种讨论并没有取得成果,但却找不到与它们的联系。

票数 4
EN

Stack Overflow用户

发布于 2019-07-29 09:51:02

大小必须至少为17个字节,因为它的最大变体有16个字节大,并且它需要一个额外的字节来进行鉴别(在某些情况下编译器可以很聪明,并将鉴别器放在未使用的变体位中,但它不能在这里这样做)。

此外,Big的大小必须是对齐 f64的8个字节的倍数。大于17的8的较小倍数是24。同样,Smol不能只有17个字节,因为它的大小必须是4个字节的倍数(f32的大小)。Smoller只包含u8,因此它可以对齐到1字节。

票数 9
EN

Stack Overflow用户

发布于 2019-07-29 09:55:20

我认为这是因为内在价值观的一致性要求。

u8的对齐方式是1,所以所有这些都可以像您预期的那样工作,并且整个大小为17个字节。

但是f32有一个4对齐(从技术上讲,它依赖于拱形,但这是最有可能的值)。因此,即使鉴别值仅为1字节,也可以得到Smol::Float的布局。

代码语言:javascript
复制
[discriminant x 1] [padding x 3] [f32 x 4] = 8 bytes

然后是Smol::Sixteen

代码语言:javascript
复制
[discriminant x 1] [u8 x 16] [padding x 3] = 20 bytes

为什么这个垫子真的有必要?因为它要求类型的大小必须是对齐的倍数,否则这种类型的数组将不对齐。

类似地,f64的对齐方式是8,所以您得到了24的完整大小,这是最小的8倍数,适合所有枚举。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57250993

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档