我有一个包含一些数据的Rust枚举。我想把它打包成尽可能少的字节。我试着像这样使用repr
:
#[repr(u8)]
enum MyEnum {
OptionA(u32),
OptionB(u32),
Nothing,
}
fn main() {
println!("{}", std::mem::size_of::<MyEnum>()); // prints 8 (should be 5)
}
字符串
理论上,这应该只需要占用5个字节(1个用于u8
判别式,4个用于u32
s判别式)。但是,无论我使用什么repr
,它都占用了整整8个字节,就好像它与4对齐一样。
官方的Rust文档清楚地表明,repr(u*)
做了你对无字段枚举的期望,但是关于带字段的枚举的部分对我来说是模糊的:
如果枚举有字段,其效果类似于repr(C)
的效果,因为有一个定义的类型布局。这使得将枚举传递给C代码,或访问类型的原始表示并直接操作其标记和字段成为可能。
所以布局已经定义好了,但是repr的参数没有做任何事情吗?或者这是一个bug?这对我来说似乎很疯狂。我知道对齐字段通常会更有性能,但是如果什么都不做,让你指定repr有什么意义呢?如果我想要内存有效的打包,我必须自己实现它,失去所有的rusts模式匹配和安全保证?
2条答案
按热度按时间kcwpcxri1#
一般来说,这个类型必须是8字节。这是因为它包含一个
u32
,这是4字节,必须对齐到一个4字节的地址,如果你有一个数组,那么数组的元素必须每个对齐到4字节的倍数,这要求大小是4的倍数。否则,你可以对对象进行mut引用,而对象不会对齐,并且引用不允许未对齐。不对齐的访问总是比较慢,在某些架构上,它也会用
SIGBUS
杀死进程。有些架构通常会让你的进程被杀死,但内核会以一个陷阱、一个上下文切换到内核、两次加载和一些移位的巨大代价来修复访问,然后从内核中切换上下文。通常这些架构上的人更喜欢SIGBUS
,因为这样至少问题是显而易见的。即使是RISC-V,最新的架构之一,不能保证快速的非对齐访问(它可能会陷入内核)。请注意,C编译器做同样的事情:
字符串
的确,有些C编译器提供压缩表示,但它们是非标准的。
Rust中有一些关于打包枚举的讨论,但它们还没有标准化,因此不可用。
ttp71kqs2#
如文档所示,
#[repr(u8)]
对带有字段的枚举有两种影响:1.有一个类型的定义布局。这使得将枚举传递给C代码,或访问类型的原始表示并直接操作其标记和字段成为可能。
1.如果判别式溢出了它必须适应的整数,它将产生编译时错误
所以它确实做了一些事情。虽然你似乎在追求的
#[repr(packed)]
目前不能在枚举上工作。无论如何,这是一个 * 非常 * 的nieche repr,并带有严重的缺点,文档甚至指出它
是不能轻易使用的。除非你有极端的要求,否则不应该使用。
如果你不顾所有的警告和可能大约0的好处仍然想这样做,你可以使用一个手动标记的工会:
字符串
你甚至可以匹配它,但是创建一个对
payload
的引用是UB(因为引用必须对齐),所以要从中读取,你必须使用read_unaligned
,但是对于任何packed
数据结构都是如此,所以unsafe
是不可避免的开始(而不是只为union
访问引入的东西)。