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

dna*_*dna 12 c# struct sizeof

MSDN中明确规定

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

C#语言规范是更精确的:

  1. 未指定成员打包到结构中的顺序.
  2. 出于对齐目的,在结构的开头,结构内和结构的末尾可能存在未命名的填充.
  3. 用作填充的位的内容是不确定的.
  4. 当应用于具有结构类型的操作数时,结果是该类型的变量中的总字节数,包括任何填充.

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

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

public struct MyEmptyStruct { }
Run Code Online (Sandbox Code Playgroud)

MyStruct我们明确地强制布局,大小,以及如何通过它打包StructLayout属性.该结构应该在内存中具有1个字节的大小.

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

当尝试使用来计算论文结构的大小sizeof(MyStruct)sizeof(MyEmptyStruct)编译器会引发以下错误:

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

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

Eri*_*ert 9

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

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


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

  • 我在C#的设计中注意到的一件事是,它有时似乎是人们不愿意禁止他们看不到用途的东西的语言,即使这些东西本来是无害的; 示例包括前面提到的sizeof,`enum`和`delegate`类型约束,声明一个方法`protected new sealed virtual`(一个`new`非虚方法不会阻止派生类重写父定义),等等.有一种驱动理念是不允许看到没有立即使用的东西,即使这样做只是意味着不检查它们? (2认同)

Han*_*ant 6

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

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

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

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

事实并非如此,实际结构可能比声明的结构小.可以通过将成员交换到填充中.

该结构应该在内存中具有1个字节的大小.

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

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

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

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

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