Marshal.SizeOf(typeof(IntPtr))与sizeof(IntPtr)的区别

4

我知道Marshal.SizeOf()sizeof()在本质上有很大不同。但是在IntPtr的情况下,无论CPU架构如何,它们都会始终返回完全相同的内容,对吗?


你能解释一下为什么你关心某个东西的内存大小和传输时的大小是否相同吗?你只是在问一个琐事问题,还是你的问题有一些目的? - Eric Lippert
等等,你在问题中说过“我知道它们在根本上有多么不同”。如果你认为它们可能是可以互换的,并且不知道何时使用其中之一,那么你又如何知道它们有什么不同呢? - Eric Lippert
1
假设我有一个函数告诉我多大的柜台空间可以容纳一个烤面包机,还有另一个函数告诉我需要多大的盒子才能邮寄我的烤面包机。请问“对于这个特定的烤面包机,这两个尺寸是否相同”有任何意义吗?也许它们是相同的,也可能不是,但是你为什么要在意呢?你会在布置厨房时使用第一个方法,在打包邮件卡车时使用第二个方法,并且你永远不会交换它们,因为那将是一个非常糟糕的主意。 - Eric Lippert
你的前提似乎是在PInvoke中使用sizeof(<type>)是错误的,因为不能保证(至少对于其他类型)它将返回与Marshal.SizeOf(typeof(<type>))相同的值。但正如我所说,如果可以_保证_,至少对于IntPtr来说,这将始终是正确的,那么在我看来,这只是最佳实践、一致性和约定 - 不一定是“错误”的或者仅仅是因为“愚蠢的运气”而起作用。并不是我主张使用sizeof,只是说一下。 - cogumel0
无论如何,问题的重点是要了解它们是否总是返回相同的结果。我不想参与讨论应该使用方法A还是方法B的辩论,这超出了讨论的范围。因为某些被认为是问题的想法而进行投票就是完全错误的。标记的答案说明它们在理论上可能不同,并回答了问题。其他任何事情都是不相关的。 - cogumel0
显示剩余6条评论
2个回答

6

首先,有原始类型:

| | x64 | x86 | | Marshal. | Marshal. | |Primitive | SizeOf() sizeof(T) | SizeOf() sizeof(T) | |========= | =========== =========== | =========== =========== | Boolean | 4 <-> 1 | 4 <-> 1 | | Byte | 1 1 | 1 1 | | SByte | 1 1 | 1 1 | | Int16 | 2 2 | 2 2 | | UInt16 | 2 2 | 2 2 | | Int32 | 4 4 | 4 4 | | UInt32 | 4 4 | 4 4 | | Int64 | 8 8 | 8 8 | | UInt64 | 8 8 | 8 8 | | IntPtr | 8 8 <-> 4 4 | | UIntPtr | 8 8 <-> 4 4 | | Char | 1 <-> 2 | 1 <-> 2 | | Double | 8 8 | 8 8 | | Single | 4 4 | 4 4 |
在.NET中,对于结构(ValueType)实例,其内部管理布局和编组图像之间可能存在显着差异,无论是总大小还是字段布局顺序。后者甚至适用于所谓的格式化类[1] 实际上很少需要关于实际管理的结构布局的信息,事实上.NET在试图使其不可发现方面付出了很大的努力。您也无法影响结构的内部布局,这正是Marshal层提供特定声明所需布局的能力的原因,以进行互操作。
这里有一个需要知道结构体运行时内存图的真实大小的用例:假设您正在使用结构体的托管数组作为某种存储 blob 概念,您希望每个块(即数组)保持在固定的总分配大小以下,比如 ~84,800 字节-很明显,在这种情况下要避免 LOH。您希望这个存储类成为一个通用类,使用任意 ValueType 类型进行参数化,该类型定义“记录”或表条目。为了确定可以放入每个托管数组块中的结构体数量,您需要在运行时发现给定结构体的真实大小,以便您可以将 84,800 除以该值。
有关 marshaling 对比 managed-internal 结构布局、填充和大小可能出现的差异的更详细的检查,请参见我对 "How do I check the number of bytes consumed by a structure?" 的扩展回答。



[1.] "一个格式化类是一种引用类型,其布局由StructLayoutAttribute属性指定,可以是LayoutKind.ExplicitLayoutKind.Sequential。" https://msdn.microsoft.com/zh-cn/library/2zhzfk83(v=vs.110).aspx

3
首先,为什么你想知道这个?如果你的代码编写得好,你不需要任何关于sizeofMarshal.SizeOf返回值的假设。你会使用marshaller来marshal你的任何实例吗?那就用Marshal.SizeOf。你的变量从未离开托管世界,或者你正在使用自定义marshalling吗?那就使用sizeof(或IntPtr.Size,因为它不需要unsafe块)。在这两种情况下,你都不需要关心它们是否返回相同的值。这就是实际的答案。
接下来是理论。根据C#语言规范sizeof(IntPtr)返回的值是“该类型变量中的总字节数,包括任何填充”(因为IntPtr是一个结构体)。然而,它还指出,这个值是“实现定义的”。所以,如果你想要技术上的答案,C#规范只是说“自己算一下”。

话虽如此,IntPtr 的文档明确指出,在 32 位平台上该类型为 32 位,在 64 位平台上为 64 位,并且 ECMA-335 文档说明 IntPtr 是一种特殊的内置类型,对应于 native int,因此我认为我们可以暂时得出结论,即在任何声称遵循规范的实现中,sizeof(IntPtr) 都是可预测的:在 32 位平台上为 4,在 64 位平台上为 8。 IntPtr.Size 是另一种选择,并且它明确记录了这一点。

Marshal.SizeOf(typeof(IntPtr)) 是一个不同的东西。它并没有明确说明它将返回什么,除了“未托管类型的大小”;在底层,它调用了CLR中的一些本地代码,询问TypeHelper获取大小的基础类型。对于IntPtr,这将返回C++中的sizeof(void*),在大多数C++编译器和平台上,32位平台上为4,64位平台上为8。

从技术上讲,sizeof(IntPtr)Marshal.SizeOf(typeof(IntPtr)) 可能是不同的。但这通常不是你需要关心的事情,因为在运行时和JIT(或AOT编译器)的组合中,使sizeof(IntPtr)等于Marshal.SizeOf(typeof(IntPtr))是很明智的,否则运行时只会让自己的生活更加困难。另一方面,正如我指出的,通常没有任何理由需要依赖它们相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接