Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why sizeof of a struct is unsafe

Tags:

c#

struct

sizeof

The MSDN clearly states

For all other types, including structs, the sizeof operator can only be used in unsafe code blocks.

The C# Language Specification is even more precise :

  1. The order in which members are packed into a struct is unspecified.
  2. For alignment purposes, there may be unnamed padding at the beginning of a struct, within a struct, and at the end of the struct.
  3. The contents of the bits used as padding are indeterminate.
  4. When applied to an operand that has struct type, the result is the total number of bytes in a variable of that type, including any padding.

However how would the CLR handle the following structures :

[StructLayout(LayoutKind.Explicit, Size = 1, Pack = 1)]
public struct MyStruct
{
    [FieldOffset(0)] public byte aByte;
}

public struct MyEmptyStruct { }

In MyStruct we enforce the layout explicitly, the size and how to pack it via the StructLayout attribute. This structure is supposed to have a size of 1 byte in memory.

On the other hand MyEmptyStruct is empty, we can assume that the size in memory will be 0 bytes - even if a such structure is most likely not going to be used it still is an interesting case.

When trying to compute the size of theses structures using sizeof(MyStruct) and sizeof(MyEmptyStruct) the compiler throws the following error :

'*' does not have a predefined size, therefore sizeof can only be used in an unsafe context

I would like to know why using sizeof in this context is considered unsafe. The question is not intended to ask for workarounds nor the correct way to compute the size of a struct but rather to focus on the causes.

like image 622
dna Avatar asked Jun 14 '13 09:06

dna


People also ask

Why is sizeof unsafe?

Because in Go if you need to call sizeof, it generally means you're manipulating memory directly, and you should never need to do that.

Why is sizeof unsafe C#?

If you send data via a socket, then it is unsafe to assume, that sender and receiver share the same platform and thus the same size. It would be safer to serialize and send first the size and then the result of a serialization. Marshal. SizeOf will fail if the struct is a generic type.

Can you use sizeof on a struct?

The sizeof for a struct is not always equal to the sum of sizeof of each individual member. This is because of the padding added by the compiler to avoid alignment issues. Padding is only added when a structure member is followed by a member with a larger size or at the end of the structure.

What is the size of struct always?

A struct that is aligned 4 will always be a multiple of 4 bytes even if the size of its members would be something that's not a multiple of 4 bytes.


2 Answers

I would like to know why using sizeof in this context is considered unsafe.

Matthew Watson's comment hits the nail on the head. What are you going to do with that information in safe code? It's not useful for anything(*). It doesn't tell you how many unmanaged bytes you need to allocate to marshal; that's Marshal.SizeOf. It's only useful for pointer arithmetic, so why should it be in the safe subset?


(*) OK to be fair there are a few odd corner case usages for a safe sizeof that can take structs that contain managed types. Suppose for example you have a generic collection class that is going to allocate a bunch of arrays and would like to ensure that those arrays are not moved into the large object heap; if you could take the size of a struct that contained managed objects then you could write this code very easily, and it would not need any pointer arithmetic. But the fact remains that sizeof was designed specifically for pointer arithmetic, and not so that you could do an end-run around the garbage collection heuristics for arrays.

like image 127
Eric Lippert Avatar answered Oct 06 '22 10:10

Eric Lippert


Lots of wrong assumptions in the question, I'll just address them one by one:

in MyStruct we enforce the layout explicitly

You didn't. The [StructLayout] attribute is only truly effective when the structure value is marshaled. Marshal.StructureToPtr(), also used by the pinvoke marshaller. Only then do you get the guarantee that the marshaled value has the requested layout. The CLR reserves the right to layout the structure as it sees fit. It will align structure members so the code that uses the struct is as fast as possible, inserting empty bytes if necessary. And if such padding bytes leave enough room then it will even swap members to get a smaller layout. This is entirely undiscoverable, other than by using a debugger to look at the machine code that accesses the structure members. Some [StructLayout] properties do affect the layout, LayoutKind.Explicit does in fact support declaring unions. The exact details of the mapping algorithm is undocumented, subject to change and strongly depends on the target machine architecture.

the result is the total number of bytes in a variable of that type, including any padding.

It is not, the actual structure can be smaller than the declared struct. Possible by swapping a member into the padding.

This structure is supposed to have a size of 1 byte in memory.

That's very rarely the case. Local variables are also aligned in memory, by 4 bytes on a 32-bit processor and 8 bytes in a 64-bit processor. Unless the struct is stored in an array, it will actually take 4 or 8 bytes on the stack or inside an object on the heap. This alignment is important for the same reason that member alignment is important.

MyEmptyStruct is empty, we can assume that the size in memory will be 0 bytes

A variable will always have at least 1 byte, even if the struct is empty. This avoids ambiguities like having a non-empty array that takes zero bytes. Also the rule in other languages, like C++.

why using sizeof in this context is considered unsafe

To be clear, using sizeof on primitive value types doesn't require unsafe since .NET 2. But for structs there is a definite possibility that sizeof() might be used to address memory directly, adding it to an IntPtr for example. With the considerable risk that using sizeof() was the wrong choice and should have been Marshal.SizeOf() instead. I would guess that the practicality of using sizeof() on structs is so low, given that a struct should always be small, and the odds for hacking IntPtrs the wrong way is so high that they left it unsafe.

like image 27
Hans Passant Avatar answered Oct 06 '22 09:10

Hans Passant