【问题标题】:Why sizeof of a struct is unsafe为什么结构的 sizeof 不安全
【发布时间】:2013-10-11 03:07:02
【问题描述】:

MSDN 明确声明

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

C# Language Specification 更加精确:

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

但是 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。该问题并非旨在寻求解决方法或正确方法来计算结构的大小,而是关注原因。

【问题讨论】:

  • Skeet 的回答在这里:sizeof() structures not known. Why? 很好。
  • 我没有看到任何地方为什么它是unsafe。我猜编译器需要它来强化sizeof(struct) 将根据x86/x64 设置等而变化的概念,所以这是一件不安全的事情。但是仅仅询问结构的大小不是unsafe,就像获取和使用指向内存块的指针是unsafe一样。
  • 投票重新开放。这不是与上面链接的问题的重复 - 另一个问题询问为什么您不能获得仅包含内置类型的 struct 的大小,而不是为什么 @ 的 sizeof 987654339@s 在托管上下文中不可用。
  • 原因在 Chris Brummes 博客文章的第一句话中概述了:blogs.msdn.com/b/cbrumme/archive/2003/04/15/51326.aspx - “我们不公开对象的托管大小,因为我们希望保留改变我们放置方式的能力这些东西都出来了。”
  • 我得出的结论是,您需要unsafe 的原因是唯一的原因您可能不得不使用sizeof() 来获取结构的大小如果您要进行指针运算,因此将使用限制为unsafe 上下文是明智的。请注意,序列化数据时不能使用sizeof(struct),因为它的大小可能与Marshal.Sizeof()不同

标签: c# struct sizeof


【解决方案1】:

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

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


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

【讨论】:

  • 您最新博文的主题非常巧合。一定是sizeof 意识周之类的;)
  • 我在 C# 的设计中注意到的一件事是,有时人们似乎会特意禁止他们认为没有用处的东西,即使这些东西否则将是无害的;例子包括前面提到的sizeof、enumdelegate类型约束、声明方法protected new sealed virtualnew非虚方法不会阻止派生类覆盖父定义)等。不允许没有立即使用的东西的哲学,即使这样做只是意味着不检查它们?
  • @supercat:这过于简单化了,但基本上你的问题的答案是肯定的。设计团队的态度是,特性应该由他们的用例来证明,如果可能的话,限制在这些用例中。设计团队也有一种矛盾的态度,即一般特征优于特定特征。设计是在一组相互冲突的原则中找到良好折衷的过程。
  • 我当然可以理解,需要大量工作才能实现的功能应该需要大量的理由。我更感兴趣的是语言设计者决定编译器编写者应该投入精力禁止他们可能没有看到太多用处的构造,但这些构造默认情况下是可用的。例如,将一个泛型类型约束到System.Delegate 不会让Invoke 它,但会允许一个人在该类型的两个事物上调用Delegate.Combine 并将结果转换回该类型(有用),所以为什么禁止吗?
  • @supercat:没有一个功能不需要大量的设计、规范、实现、测试和文档工作,也没有一个功能不会影响每个未来功能的设计成本。您建议的功能很好,我很想拥有它;您已经提到了一些必须测试的案例。这不是一个糟糕的功能,如果我有它,我会使用它——我们在添加表达式树时考虑过它——但它并没有成为标准。 C# 3.0 是那个版本的 VS 中最大的单个工作项;任何增加风险的东西都被削减了。
【解决方案2】:

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

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

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

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

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

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

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

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

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

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

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

【讨论】:

  • 感谢您的详细回答,但我仍有几个问题。 the result is the total number of bytes in a variable of that type, including any padding. => It is not ... 所以 C# 规范实际上是错误的? That's very rarely the case. Local variables are also aligned in memory 当然,但是 sizeof 运算符未定义为返回类型的对齐/填充大小,Marshal.SizeOf() 就是为此而设计的。
  • 另外You didn't. The [StructLayout] attribute is only truly effective when the structure value is marshaled. MSDN 声明相反:...LayoutKind.Explicit ... This affects both managed and unmanaged layout, for both blittable and non-blittable typesmsdn.microsoft.com/en-us/library/…
  • 您要回答的问题太多。我专门解决了工会的极端情况。
  • 如果我对您的回答有很多意见,请接受我的歉意,但我认为有几点需要澄清。
  • 导致IntPtr 被实际取消引用的代码将无法验证。添加sizeof(someStruct) 不会比添加12 更难以验证。将sizeof(someStruct) 添加到IntPtr 但实际上从未导致它被取消引用的代码不会比将任何其他数字添加到任何其他变量的代码更不安全。
猜你喜欢
  • 2022-04-26
  • 2020-12-02
  • 2013-06-19
  • 2019-10-27
  • 2012-06-14
  • 2019-08-17
  • 2015-08-21
  • 2013-09-24
  • 2019-10-18
相关资源
最近更新 更多