msdn清楚地说明
For all other types, including structs, the sizeof operator can only
be used in unsafe code blocks.
C语言规范更加精确:
未指定成员打包到结构中的顺序。
出于对齐目的,可能在开始处有未命名的填充结构的,在结构内,在结构的末尾。
用作填充的位的内容不确定。
当应用于具有结构类型的操作数时,结果是该类型变量中的字节总数,包括任何填充。
但是,clr将如何处理以下结构:
1 2 3 4 5 6 7
| [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)计算这些结构的大小时,编译器抛出以下错误:
'*' does not have a predefined size, therefore sizeof can only
be used in an unsafe context
我想知道为什么在这种情况下使用sizeof被认为是unsafe。这个问题不是为了寻求解决方法,也不是计算结构大小的正确方法,而是关注原因。
- 这里是双向飞碟的答案:sizeof()结构未知。为什么?很好。
- 我没有看到任何地方是因为unsafe。我猜想编译器需要强化这样一个概念,即sizeof(struct)将根据x86/x64设置等发生变化,因此这是一种不安全的做法。但是仅仅要求一个结构的大小并不是unsafe,这与获取和使用指向内存块的指针unsafe的方式相同。
- 投票重新开始。这不是上面链接的问题的副本-另一个问题问为什么您不能得到一个只有内置类型的struct的大小,而不是为什么struct的sizeof在托管上下文中不可用。
- 原因在ChrisBrummes博客条目的第一句话中概述:blogs.msdn.com/b/cbrumme/archive/2003/04/15/51326.aspx-"我们不公开对象的托管大小,因为我们希望保留更改这些内容布局方式的能力。"
- @斯宾德,这是一个很好的答案,但它回答了一个微妙的不同问题。这个问题的标题隐藏了它,但是操作人员只想知道为什么他不能得到一个由原语组成的结构的大小,而不是为什么它一般被禁止。
- 我得出的结论是,您需要unsafe的原因是,使用sizeof()的结构的大小可能是因为要进行指针运算,因此将使用限制在unsafe上下文是明智的。请注意,在序列化数据时不能使用sizeof(struct),因为它可能与Marshal.Sizeof()的大小不同。
- 我认为这一限制是由C设计师"为了你自己的利益"引入的。他们的逻辑是这样的:"你不能利用sizeof返回的值,除非你能访问struct的内存,所以你最好先不知道sizeof"。虽然他们是对的,但是有一个简单的解决方法,可以让您访问sizeof:link。
- @Speder感谢您的链接,仍然考虑使用StructLayout属性完全修饰的空结构或结构。我们是否仍然不能保证结构在内存中不会有如此精确的布局?我应该编辑我的问题以便更清楚地表达吗?
- StructLayout不需要不安全的代码吗?在这种情况下,可以使用sizeof对吗?
- @如果背后的原因是相同的,那么dasbinkenlight也可能是微妙的重复!我想感谢你的解释,也感谢你的恶劣的工作环境。我不知道这个!
- @DNA不客气!我希望这个问题很快能被重新打开(它有五分之四的选票需要重新打开),这样我就可以把这个评论作为一个正常的答案发表。
- @我不认为StructLayout需要不安全的代码。你为什么这么说?此外,我可以在不使用不安全开关的情况下进行编译。
- 哎呀。我的错。我可能需要多读一些关于这个的资料。
- 为了更清楚起见,我已经编辑了我的问题
- @请稍等,问题又开始了。
- 注意,问题底部结构的大小不是1,它是2,.NET中的一个字符是16位。
- @拉塞尔诉卡尔森一案谢谢你发现打字错误!
- @Karlsen:我查看了一个链接,建议不允许使用sizeof()来允许将来的更改。我建议未来更改的可能性是提供sizeof()功能的一个原因,或者至少是一种了解"这种类型的数组在符合gen0集合条件时可以有多少个元素"的方法。可以肯定的是,除非元素太大而无法实际应用,否则必须猜测应该生成一个大小约为84999/size of(element)的数组是一种黑客行为,但是……
- @卡尔森:……在猜测元素的大小时必须进行这样的计算,这更糟糕。当然,有一种方法询问数组大小可以避免LOH,和/或有一个"如果可能的话,在gen0堆中创建数组"的方法可能更好,但据我所知,这些方法在运行时或框架中并不存在,而确定对象大小的能力存在,但不被许多语言公开。
- 哦,我同意。但是所有的证据都表明这仅仅是编译器设计团队做出的决定,而不是背后的技术原因。我很想让"你能在爱情开始发挥作用之前把它做得多大"自己计算一下。
- @卡拉森:我想知道.NET提供方法CreateShortLivedArray(int Size)和CreateLongLivedArray(int Size)是否会有特别的困难,可能会有接受复制现有数组的多个大小或版本的重载?如果在gen0堆上分配了一个大于85000字节的数组,会发生什么"坏"的情况,或者如果下一个gen2集合循环时存在对数组的25%的引用,那么这种分配会不会像loh应用程序那样"预期"不执行?
I would like to know why using sizeof in this context is considered unsafe.
马修·沃森的评论直言不讳。你打算用安全代码处理这些信息吗?它对任何东西都没有用处。它不会告诉您需要分配多少非托管字节来封送;这就是Marshal.SizeOf。它只对指针算术有用,那么为什么它应该在安全子集中呢?
(*)可以公平地说,对于安全的sizeof有一些奇怪的用法,它们可以采用包含托管类型的结构。例如,假设您有一个通用集合类,它将分配一组数组,并希望确保这些数组不会移动到大型对象堆中;如果您可以采用包含托管对象的结构的大小,那么您可以非常容易地编写此代码,并且它不需要任何指针算术。但事实仍然是,sizeof是专门为指针算术而设计的,而不是为了让您可以围绕数组的垃圾收集启发进行最终运行。
- 你最近一篇博文的主题完全是巧合。一定是sizeof意识周之类的;
- 我在设计C时注意到的一件事是,有时语言似乎会让人不由自主地禁止他们看不到使用的东西,即使这些东西是无害的;例如前面提到的sizeof、enum和delegate类型的约束,声明方法protected new sealed virtual(EDOCX1x1〔14〕非虚方法不会阻止派生类重写父定义),等等。是否有一种驱动原理不允许看到没有立即使用的事物,即使这样做也仅仅意味着不检查它们?
- @超级卫星:这过于简单化了,但基本上你的问题的答案是肯定的。设计团队的态度是,特性应该由它们的用例来证明,如果可能的话,应该限制在这些用例中。设计团队也有一种矛盾的态度,即一般特性比具体特性更好。设计是在一系列相互冲突的原则中找到一个好的妥协的过程。
- 我当然可以理解,需要大量工作来实现的特性需要大量的理由。我更感兴趣的是,在某些情况下,语言设计人员决定编译器编写人员应该投入精力,禁止使用那些他们可能看不到多大用处但默认情况下可以使用的构造。例如,将一个泛型类型约束到System.Delegate不会让一个Invoke它,而是允许一个对该类型的两个事物调用Delegate.Combine并将结果强制转换回该类型(有用),那么为什么禁止它呢?
- @Supercat:没有不需要大量设计、规范、实现、测试和文档工作的特性,也没有不影响未来每个特性的设计成本的特性。您建议的功能是一个很好的功能,我很想拥有它;您已经提到了一些需要测试的案例。这不是一个糟糕的特性,如果我有它的话,我会使用它——我们在添加表达式树时考虑过它——但是它没有成为一个条。C 3.0是那个版本的vs中最大的一个工作项;任何增加风险的东西都被削减了。
- @Ericlipper:简单地从禁止的约束类型列表中省略System.Enum、System.Delegate和System.MulticastDelegate,需要做什么工作?这些类型的约束来自于我理解的CIL规范中允许的内容,并且在实践中,正如规范在CIL中实现时所建议的那样,那么为什么要将这些类型单独列出以进行排除呢?
- @Supercat:我不知道细节;在做出决定时,我不在C团队,而实施团队在微软剑桥研究院。如果你想得到某个人的明确答复,你应该向安德鲁·肯尼迪提出你的问题。
- 非常感谢你的评论。我有最后一个问题。如何理解msdn.microsoft.com/en-us/library/…上的msdn。尤其是在关于Explicit布局的Remarks中?这是否意味着结构的布局在托管世界中实际上与在非托管世界中相同?如果是这样,那么我们能说在这种特殊情况下,依靠sizeof进行未经调整的分配是安全的吗?
- @DNA:我认为文档对于属性何时影响Blittable结构的托管内存中的布局非常清楚。至于你的第二个问题:当正确的工具如此容易使用时,为什么你会依赖错误的工具来完成工作?如果您正在封送,需要知道大小,那么使用适当命名的Marshal.SizeOf方法。做其他事情有什么好处?
- @埃里克:我只是好奇而已。然而,我不认为这一文件是明确的:这种影响实际上可能意味着很多事情。但再次感谢你的回答!
- @Supercat:我相信我在某个地方读到,这些类型被省略了,因为您真正想要的是将一个类型约束到一个严格的子类型。也就是说,如果类型参数是System.Enum本身,这通常是无稽之谈。我碰巧发现治疗比疾病更糟糕,但有一个潜在的理由。
- @kvb:如果一个类型同时约束到struct和System.Enum,我认为泛型将只接受适当的枚举类型。虽然需要使用一些反射来做任何有用的事情,但是静态EnumMasker.HasFlag()方法(可以从静态EnumMasker.HasFlag(this T it)扩展方法调用)可以以几乎10倍于Enum.HasFlag的速度实现性能。似乎很有用。至于Delegate,即使T是Delegate,组合委托并返回正确类型委托的方法似乎也很有用。
- @Supercat:我不反对;我只是想指出,有一些(也许不是强制的)原因来阻止这些类型被用作约束。如果您真的想使用它们,请考虑使用f:-)
- @kvb:就我个人而言,我怀疑问题在于,约束仅被视为一种允许使用泛型参数的类或方法以不可能的方式使用它的方法,而不是一种确保它们仅在有意义的情况下才有用的方法。从这个角度来看,对Nullable的讨厌的struct约束可能被认为是需要struct约束的唯一原因(我想不出它允许人们做什么,除了声明T?之外),尽管有些情况下……
- …一个Foo不可能被期望与一个非结构化的T一起有效地工作[一个类可能期望复制一个实现可变接口(例如IEnumerator的T将复制它所代表的状态]。我认为从表现力的角度来看待语言特征要好得多。从直接代码执行的角度来看,仅用于封装除标识之外的不可变状态的引用类型存储位置与也封装标识、可变状态或两者的引用类型存储位置之间可能没有区别,但是……
- …如果类型系统包含这样的概念,那么围绕深度和浅层操作(例如克隆或等效操作)的许多混乱,或者从方法或属性返回的对象的所有权都将得到缓解。例如,在myItems = Foo.Items; Foo.AddItem(...);之后,myItems会包含新项目吗?表示它封装了目标的可变状态的返回类型意味着一个可变快照;表示它封装了目标的标识的返回类型意味着一个活动视图。
在这个问题中有很多错误的假设,我将逐一解决:
in MyStruct we enforce the layout explicitly
您没有。只有在整理结构值时,[structlayout]属性才真正有效。marshal.structureToptr(),也由pinvoke marshaller使用。只有这样,才能保证封送值具有请求的布局。CLR保留在其认为合适时对结构进行布局的权利。它将对齐结构成员,以使使用该结构的代码尽可能快,并在必要时插入空字节。如果这样的填充字节留下足够的空间,那么它甚至会交换成员以获得更小的布局。这是完全不可恢复的,除了使用调试器查看访问结构成员的机器代码。某些[structlayout]属性确实会影响layout、layoutKind。explicit实际上支持声明联合。映射算法的确切细节是未记录的,可能会发生更改,并且很大程度上取决于目标机器结构。
the result is the total number of bytes in a variable of that type, including any padding.
不是,实际结构可以小于声明的结构。可以通过将成员替换到填充中来实现。
This structure is supposed to have a size of 1 byte in memory.
这种情况很少发生。本地变量也在内存中对齐,在32位处理器上为4个字节,在64位处理器上为8个字节。除非结构存储在数组中,否则它在堆栈上或在堆上的对象内实际将占用4或8个字节。此对齐非常重要,原因与构件对齐非常重要相同。
MyEmptyStruct is empty, we can assume that the size in memory will be 0 bytes
即使结构为空,变量也将始终至少有1个字节。这样就避免了不明确的地方,比如有一个非空数组,它占用零字节。还有其他语言的规则,比如C++。
why using sizeof in this context is considered unsafe
要清楚,在基元值类型上使用sizeof并不要求不安全,因为.NET 2。但是对于结构来说,sizeof()可能被用于直接寻址内存,例如将其添加到intptr中。有相当大的风险,使用sizeof()是错误的选择,应该改为marshal.sizeof()。我猜在结构上使用sizeof()的实用性是如此之低,因为结构应该总是很小的,而以错误的方式入侵intptr的几率是如此之高,以至于它们使其不安全。
- 谢谢你的详细回答,不过我还有几个问题。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当然可以,但size of运算符没有定义为返回类型的对齐/填充大小,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中,但实际上从未导致取消引用的代码将不会比将任何其他数字添加到任何其他变量中的代码更不安全。