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

时间:2013-06-14 09:38:10

标签: c# struct sizeof

MSDN明确说明

  

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

C# Language Specification更精确:

  1. 未指定成员打包到结构中的顺序。
  2. 出于对齐目的,开头可能有未命名的填充 结构,结构内部和结构的末尾。
  3. 用作填充的位的内容是不确定的。
  4. 当应用于具有结构类型的操作数时,结果是该类型变量中的总字节数,包括任何填充。
  5. 然而,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。问题不是要求解决方法,也不是正确的方法来计算结构的大小,而是关注原因。

2 个答案:

答案 0 :(得分:9)

  

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

马修沃特森的评论击中了头部。 你打算用安全代码对这些信息做什么?它对任何事情都没有用(*)。它不会告诉您需要为编组分配多少非托管字节;那是Marshal.SizeOf。它仅对指针算法有用,那为什么它应该在安全子集中​​呢?


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

答案 1 :(得分:6)

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

  

在MyStruct中我们明确强制执行布局

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

  

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

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

  

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

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

  

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

变量总是至少有1个字节,即使结构为空。这避免了诸如具有占空零字节的非空数组之类的含糊之处。也是其他语言的规则,比如C ++。

  

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

要明确的是,在原始值类型上使用sizeof不需要 unsafe ,因为.NET 2.但是对于结构体,sizeof()可能会被用于直接寻址内存,例如,将它添加到IntPtr。使用sizeof()是错误的选择并且应该是Marshal.SizeOf()的风险相当大。我猜想在结构上使用sizeof()的实用性是如此之低,因为结构应该总是很小,并且以错误的方式攻击IntPtrs的几率是如此之高以至于它们使它不安全