简体   繁体   English

如何在忽略判别式的情况下找到枚举的大小?

[英]How can I find the size of an enum while ignoring the discriminant?

The Rust Reference documents that a Rust enum annotated with #[repr(C)] can be viewed as a C struct of two fields. Rust 参考文档中,使用#[repr(C)]注释的 Rust 枚举可以被视为两个字段的 C 结构。 The first field is a C enum for the discriminant, the second field is a C union of C structs corresponding to the fields of the enum's variants.第一个字段是判别式的 C 枚举,第二个字段是 C 与 enum 变体的字段相对应的 C 结构的并集。

Due to a bug in an FFI interoperation library, I need to avoid using unions that are exactly 8 bytes.由于 FFI 互操作库中的错误,我需要避免使用正好是 8 个字节的联合。 To that end, I wanted to add some static assertions to my Rust code so I would be aware of any problematic enums.为此,我想在我的 Rust 代码中添加一些 static 断言,这样我就会知道任何有问题的枚举。 I do not know how to ask the compiler for the size of the generated union type (or equivalently, the size of the enum without accounting for the discriminant):我不知道如何向编译器询问生成的联合类型的大小(或者等效地,不考虑判别式的枚举大小):

#[repr(C)]
enum UnionSizeIs8Bytes {
    A(u8),
    B(u64),
}

#[repr(C)]
enum UnionSizeIsNot8Bytes {
    A(u8),
    B(u16),
}

const _: () = {
    // Should fail, but does not
    assert!(8 != std::mem::size_of::<UnionSizeIs8Bytes>());

    // Should not fail, but does
    assert!(8 != std::mem::size_of::<UnionSizeIsNot8Bytes>());
};

Reading The Book about repr(C) field-less enums :阅读有关repr(C)无字段枚举的书:

[...] the C representation has the size and alignment of the default enum size and alignment for the target platform's C ABI. [...] the C representation has the size and alignment of the default enum size and alignment for the target platform's C ABI.

That is, they try to be fully compatible with C enums.也就是说,它们尝试与C枚举完全兼容。

And in the next section about struct-like enums :在下一节关于类似结构的枚举中:

[..] is a repr(C) struct with two fields: [..] 是一个 repr(C) 结构体,有两个字段:

  • a repr(C) version of the enum with all fields removed ("the tag")删除了所有字段的枚举的 repr(C) 版本(“标签”)
  • a repr(C) union of repr(C) structs for the fields of each variant that had them ("the payload")具有它们的每个变体的字段的 repr(C) 结构的 repr(C) 联合(“有效负载”)

That is, your enum:也就是说,您的枚举:

#[repr(C)]
enum UnionSizeIs8Bytes {
    A(u8),
    B(u64),
}

has the same layout as this other one:具有与另一个相同的布局:

#[repr(C)]
enum UnionSizeIs8Bytes_Tag {
    A,
    B,
}
#[repr(C)]
union UnionSizeIs8Bytes_Union {
   a: u8,
   b: u64,
}
#[repr(C)]
struct UnionSizeIs8Bytes_Explicit {
    tag: UnionSizeIs8Bytes_Tag,
    data: UnionSizeIs8Bytes_Union,
}

Now, what is the actual size and alignment of an enum in C?现在,C 中枚举的实际大小和 alignment 是多少? It seems that even experts do not fully agree in the details.似乎即使是专家也不完全同意细节。 In practice most mainstream C compilers define the underlying type of an enum as a plain int , that will be an i32 or u32 .在实践中,大多数主流 C 编译器将枚举的底层类型定义为普通int ,即i32u32

With that in mind, the layout of your examples should be straightforward:考虑到这一点,示例的布局应该很简单:

  • UnionSizeIs8Bytes : UnionSizeIs8Bytes

    • 0-4: tag 0-4:标签
    • 4-8: padding 4-8:填充
    • 8-16: union 8-16:联合
      • 8-9: u8 8-9: u8
      • 8-16: u64 8-16: u64
    • Size: 16, alignment: 8尺寸:16,alignment:8
  • UnionSizeIsNot8Bytes : UnionSizeIsNot8Bytes

    • 0-4: tag 0-4:标签
    • 4-6: union: 4-6:联合:
      • 4-5: u8 4-5: u8
      • 4-6: u16 4-6: u16
    • 6-8: padding 6-8:填充
    • Size: 8, alignment: 4尺寸:8,alignment:4

Note that the alignment of a repr(C) enum is never less than that of the tag, that is 4 bytes using the above assumptions.请注意, repr(C)枚举的 alignment 永远不会小于标签的值,即使用上述假设的 4 个字节。

To compute the size of the data without the tag, you just have to subtract to the full size the value of the alignment.要计算没有标签的数据大小,您只需将 alignment 的值减去完整大小。 The alignment value will account for the size of the tag itself plus any needed padding. alignment 值将考虑标签本身的大小加上任何所需的填充。

const fn size_of_enum_data<T>() -> usize {
    std::mem::size_of::<T>() - std::mem::align_of::<T>()
}

If you want to be extra sure you could subtract std::mem::align_of::<T>().max(std::mem::size_of::<i32>()) , in case your architecture's i32 does not have alignment equal to 4, but unfortunately max doesn't seem to be const yet.如果你想更加确定你可以减去std::mem::align_of::<T>().max(std::mem::size_of::<i32>()) ,以防你的架构的i32没有alignment 等于 4,但不幸的是max似乎还不是const You could write an if of course, but that gets ugly, something like:你当然可以写一个if ,但这会变得很难看,比如:

const fn size_of_enum_data<T>() -> usize {
    let a = std::mem::align_of::<T>();
    let i = std::mem::size_of::<i32>();
    std::mem::size_of::<T>() - if a > i { a }  else { i }
}

And if you want to be extra, extra sure, you can use c_int instead of i32 .如果你想更加确定,你可以使用c_int而不是i32 But then for esoteric architectures where c_int != i32 maybe the C enum equals C int may not hold either...但是对于c_int != i32的深奥架构,可能C enum等于C int可能也不成立...

Then your assertions would be (playground) :那么你的断言将是(playground)

const _: () = {
    // It fails
    assert!(8 != size_of_enum_data::<UnionSizeIs8Bytes>());

    // It does not fail
    assert!(8 != size_of_enum_data::<UnionSizeIsNot8Bytes>());
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM