为什么结构体的sizeof是不安全的

本文关键字:不安全 sizeof 为什么 结构体 | 更新日期: 2023-09-27 18:18:36

MSDN明确说明

对于所有其他类型,包括结构体,sizeof操作符只能可用于不安全的代码块。

c#语言规范更精确:

  1. 结构体中成员的打包顺序未指定。
  2. 为了对齐的目的,可能在开始处有未命名的填充
  3. 用作填充的位的内容是不确定的。
  4. 当应用于struct类型的操作数时,结果是该类型变量的总字节数,包括任何填充。

但是CLR如何处理以下结构:

[StructLayout(LayoutKind.Explicit, Size = 1, Pack = 1)]
public struct MyStruct
{
    [FieldOffset(0)] public byte aByte;
}
public struct MyEmptyStruct { }

MyStruct中,我们显式地强制布局,大小以及如何通过StructLayout属性打包它。该结构假定在内存中的大小为1字节。

另一方面,MyEmptyStruct是空的,我们可以假设内存中的大小将是0字节-即使这样的结构很可能不会被使用,它仍然是一个有趣的情况。

当尝试使用sizeof(MyStruct)sizeof(MyEmptyStruct)计算这些结构的大小时,编译器抛出以下错误:

'*'没有预定义的大小,因此sizeof只能在不安全的上下文中使用

我想知道为什么在这种情况下使用sizeof被视为unsafe。这个问题不是要问解决方法,也不是要问正确的方法来计算结构体的大小,而是要关注原因。

为什么结构体的sizeof是不安全的

我想知道为什么在这种情况下使用sizeof被认为是不安全的。

Matthew Watson的评论一针见血。你打算用安全代码来处理这些信息吗?对于任何(*)都没有用。它不会告诉你需要分配多少非托管字节给封送;那是Marshal.SizeOf。它只对指针运算有用,那么为什么它应该在安全子集中呢?


(*)好吧,公平地说,对于可以接受包含托管类型的结构的安全sizeof来说,有一些奇怪的极端情况。例如,假设你有一个通用的集合类,它将分配一堆数组,并希望确保这些数组不会移动到大对象堆中;如果您可以获取包含托管对象的结构体的大小,那么您可以非常轻松地编写此代码,并且不需要任何指针算术。但事实仍然是,sizeof是专门为指针算术设计的,而不是为了让您可以围绕数组的垃圾收集启发式进行最终运行。

问题中有很多错误的假设,我将逐一解决:

在MyStruct中,我们显式地执行布局

你没有。[StructLayout]属性只有在结构值被封送时才真正有效。structuretoptr(),也由pinvoke编组器使用。只有这样,您才能得到保证封送值具有所请求的布局。CLR保留按其认为合适的方式布局结构的权利。它将对齐结构体成员,以便使用结构体的代码尽可能快,必要时插入空字节。如果这些填充字节留下足够的空间,那么它甚至会交换成员以获得更小的布局。这是完全无法发现的,除非使用调试器查看访问结构成员的机器码。一些 [StructLayout]属性确实影响布局,LayoutKind。Explicit实际上支持声明联合。映射算法的确切细节没有文档记录,可能会发生变化,并且很大程度上取决于目标机器的体系结构。

结果是该类型变量的总字节数,包括任何填充。

如果不是,则实际结构可以小于声明的结构。可以通过将成员交换到填充符中来实现。

这个结构体在内存中的大小应该是1字节。

这种情况很少发生。局部变量也在内存中对齐,在32位处理器上对齐4个字节,在64位处理器上对齐8个字节。除非结构体存储在数组中,否则它实际上会在堆栈或堆上的对象中占用4或8个字节。这种对齐方式的重要性与成员对齐方式的重要性是一样的。

MyEmptyStruct为空,我们可以假设内存中的大小为0字节

变量总是至少有1个字节,即使结构体是空的。这避免了歧义,例如使用一个非空数组来获取零字节。

为什么在这种情况下使用sizeof被认为是不安全的

需要说明的是,从。net 2开始,在基本值类型上使用sizeof不再需要不安全。但是对于结构体来说,确实有可能使用sizeof()直接寻址内存,例如将其添加到IntPtr中。使用sizeof()是错误的选择,应该使用Marshal.SizeOf(),这存在相当大的风险。我猜想,在结构体上使用sizeof()的实用性是如此之低,因为结构体应该总是很小的,而以错误的方式入侵intptr的几率是如此之高,以至于它们使其不安全