13

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。该问题不是要寻求解决方法,也不是要计算结构大小的正确方法,而是要关注原因。

4

2 回答 2

11

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

马修·沃森的评论一针见血。您打算如何处理安全代码中的这些信息?它对任何东西都没有用(*)。它没有告诉你需要分配多少非托管字节给封送;那是Marshal.SizeOf。它只对指针算术有用,那么为什么它应该在安全子集中​​呢?


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

于 2013-06-14T15:17:50.450 回答
7

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

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

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

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

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

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

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

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

一个变量总是至少有 1 个字节,即使结构是空的。这避免了歧义,例如使用零字节的非空数组。还有其他语言的规则,比如 C++。

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

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

于 2013-06-14T12:57:00.120 回答