Reputation: 2671
I know how fundamentally different Marshal.SizeOf()
and sizeof()
are. But in the case of an IntPtr
, won't they both always return exactly the same thing regardless of CPU architecture?
Upvotes: 4
Views: 4001
Reputation: 18759
For one thing, there's the primitive types:
| x64 | x86
| Marshal. | Marshal.
Primitive | SizeOf<T>() sizeof(T) | SizeOf<T>() 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
Beyond this, for struct (ValueType
) instances in .NET there can be significant differences between the internal managed layout and the marshaling image, both in total size and also the field layout ordering. The latter is true even for so-called formatted classes.[1]
It's rare to actually need information about the actual managed struct layout, and in fact .NET goes to great lengths in trying to make it undiscoverable. You also can't influence the internal layout of structs, and that's precisely why the Marshal
layer provides the ability to specifically declare whatever layout you need for interop.
Here's one use case for needing to know the true size of a struct's runtime memory image: Let's say you are using managed arrays of structs for some kind of storage blob concept and you want each chunk (array, that is) to stay under a fixed total allocation size, say ~84,800 bytes--obviously in this case to stay out of the LOH. You want this storage class to be a generic class, parameterized with an arbitrary ValueType
type which defines the 'records' or table entries. To determine the number of structs that can go into each managed array chunk, you'd need to discover the true size of the structure given at runtime, so you can divide 84,800 by that value.
For a more detailed examination of the differences that can arise between marshaling versus managed-internal struct layout, padding, and size, please see my extended answer to "How do I check the number of bytes consumed by a structure?"
[1.] "A formatted class is a reference type whose layout is specified by the StructLayoutAttribute attribute, as either LayoutKind.Explicit or LayoutKind.Sequential."
https://msdn.microsoft.com/en-us/library/2zhzfk83(v=vs.110).aspx
Upvotes: 6
Reputation: 28789
First off, why do you want to know? If your code is well-written, you should have no need for any assumptions about what sizeof
and Marshal.SizeOf
return. Will you be using the marshaller to marshal any of your IntPtr
instances? Then use Marshal.SizeOf
. Is your variable never leaving the managed world or are you using custom marshalling? Then use sizeof
(or IntPtr.Size
since it requires no unsafe
block). In neither case will you be concerned with whether these return the same value. That's the practical answer.
On to the theory. According to the C# language specification, the value returned for sizeof(IntPtr)
is "the total number of bytes in a variable of that type, including any padding" (since IntPtr
is a structure). However, it also notes that this value is "implementation-defined". So, if you want to get technical, the C# spec simply says "figure it out".
That said, the documentation for IntPtr
makes it clear that the type is 32-bit on 32-bit platforms and 64-bit on 64-bit platforms, and ECMA-335 documents that IntPtr
is a special built-in type that corresponds to a native int
, so I think we're allowed to tentatively conclude that sizeof(IntPtr)
is predictable on any implementation that claims to follow the specifications: 4 on 32-bit platforms, 8 on 64-bit platforms. IntPtr.Size
is an alternative, and it explicitly documents just that.
Marshal.SizeOf(typeof(IntPtr))
is a different beast. It doesn't document exactly what it will return other than "the size of the unmanaged type"; under water, it invokes a bit of native code in the CLR that asks the TypeHelper
for the underlying type for the size. For IntPtr
, this will return sizeof(void*)
(in C++), which is, of course, 4 on 32-bit platforms and 8 on 64-bit platforms for the vast majority of C++ compilers and platforms.
It is technically, theoretically, possible for sizeof(IntPtr)
and Marshal.SizeOf(typeof(IntPtr))
to be different. But this is not something you'd concern yourself with in general, because it wouldn't be sensible for the combination of runtime and jitter (or AOT compiler) to not make sizeof(IntPtr)
equal to Marshal.SizeOf(typeof(IntPtr))
-- otherwise the runtime is just making its own life more difficult. On the other hand, as I pointed out, there's not typically any reason why you would need to rely on them being the same either.
Upvotes: 4