[ACCEPTED]-Why does LayoutKind.Sequential work differently if a struct contains a DateTime field?-structlayout

Accepted answer
Score: 20

Why does LayoutKind.Sequential work differently 17 if a struct contains a DateTime field?

It 16 is related to the (surprising) fact that DateTime itself has layout "Auto" (link to SO question by myself). This code reproduces 15 the behavior you saw:

static class Program
{
    static unsafe void Main()
    {
        Console.WriteLine("64-bit: {0}", Environment.Is64BitProcess);
        Console.WriteLine("Layout of OneField: {0}", typeof(OneField).StructLayoutAttribute.Value);
        Console.WriteLine("Layout of Composite: {0}", typeof(Composite).StructLayoutAttribute.Value);
        Console.WriteLine("Size of Composite: {0}", sizeof(Composite));
        var local = default(Composite);
        Console.WriteLine("L: {0:X}", (long)(&(local.L)));
        Console.WriteLine("M: {0:X}", (long)(&(local.M)));
        Console.WriteLine("N: {0:X}", (long)(&(local.N)));
    }
}

[StructLayout(LayoutKind.Auto)]  // also try removing this attribute
struct OneField
{
    public long X;
}

struct Composite   // has layout Sequential
{
    public byte L;
    public double M;
    public OneField N;
}

Sample output:

64-bit: True
Layout of OneField: Auto
Layout of Composite: Sequential
Size of Composite: 24
L: 48F050
M: 48F048
N: 48F058

If we 14 remove the attribute from OneField, things behave 13 as expected. Example:

64-bit: True
Layout of OneField: Sequential
Layout of Composite: Sequential
Size of Composite: 24
L: 48F048
M: 48F050
N: 48F058

These example are with 12 x64 platform compilation (so the size 24, three 11 times eight, is unsurprising), but also 10 with x86 we see the same "disordered" pointer 9 addresses.

So I guess I can conclude that 8 the layout of OneField (resp. DateTime in your example) has 7 influence on the layout of the struct containing 6 a OneField member even if that composite struct 5 itself has layout Sequential. I am not sure if this 4 is problematic (or even required).


According 3 to comment by Hans Passant in the other 2 thread, it no longer makes an attempt to keep it sequential when one of the members is an Auto layout 1 struct.

Score: 7

Go read the specification for layout rules 55 more carefully. Layout rules only govern the layout when the object is exposed in unmanaged memory. This means that the compiler 54 is free to place the fields however it wants 53 until the object is actually exported. Somewhat 52 to my surprise, this is even true for FixedLayout!

Ian 51 Ringrose is right about compiler efficiency 50 issues, and that does account for the final 49 layout that is being selected here, but 48 it has nothing to do with why the compiler 47 is ignoring your layout specification.

A 46 couple of people have pointed out that DateTime 45 has Auto layout. That is the ultimate source 44 of your surprise, but the reason is a bit 43 obscure. The documentation for Auto layout 42 says that "objects defined with [Auto] layout 41 cannot be exposed outside of managed code. Attempting 40 to do so generates an exception." Also note 39 that DateTime is a value type. By incorporating 38 a value type having Auto layout into your 37 structure, you inadvertently promised that 36 you would never expose the containing structure to 35 unmanaged code (because doing so would expose 34 the DateTime, and that would generate an 33 exception). Since the layout rules only 32 govern objects in unmanaged memory, and 31 your object can never be exposed to unmanaged 30 memory, the compiler is not constrained 29 in its choice of layout and is free to do 28 whatever it wants. In this case it is reverting 27 to the Auto layout policy in order to achieve 26 better structure packing and alignment.

There! Wasn't 25 that obvious!

All of this, by the way, is 24 recognizable at static compile time. In 23 fact, the compiler is recognizing it in order 22 to decide that it can ignore your layout 21 directive. Having recognized it, a warning 20 here from the compiler would seem to be 19 in order. You haven't actually done anything 18 wrong, but it's helpful to be told when 17 you've written something that has no effect.

The 16 various comments here recommending Fixed 15 layout are generally good advice, but in 14 this case that wouldn't necessarily have 13 any effect, because including the DateTime 12 field exempted the compiler from honoring 11 layout at all. Worse: the compiler isn't 10 required to honor layout, but it is free to honor layout. Which 9 means that successive versions of CLR are 8 free to behave differently on this.

The treatment 7 of layout, in my view, is a design flaw 6 in CLI. When the user specifies a layout, the 5 compiler shouldn't go lawyering around them. Better 4 to keep things simple and have the compiler 3 do what it is told. Especially so where 2 layout is concerned. "Clever", as we all 1 know, is a four letter word.

Score: 3

A few factors

  • doubles are a lot faster if they are aligned
  • CPU caches may work better if there are no “holes” in the struck

So the C# compiler has a few 6 undocumented rules it uses to try to get 5 the “best” layout of structs, these rules may 4 take into account the total size of a struct, and/or 3 if it contains another struct etc. If you need to know the layout of a struct then you should specify it yourself rather than letting the compiler decide.

However 2 the LayoutKind.Sequential does stop the 1 compiler changing the order of the fields.

Score: 3

To answer my own questions (as advised):

Question: "Does 9 this behaviour have any ramifications when 8 doing interop with C/C++ structs that use 7 the Com DATETIME type?"

Answer: No, because 6 the layout is respected when using Marshalling. (I 5 verified this empirically.)

Question "Can 4 anyone provide an explanation?".

Answer: I'm 3 still not sure about this, but since the 2 internal representation of a struct is not 1 defined, the compiler can do what it likes.

Score: 2

You're checking the addresses as they are 14 within the managed structure. Marshal attributes 13 have no guarantees for the arrangement of 12 fields within managed structures.

The reason 11 it marshals correctly into native structures, is 10 because the data is copied into native memory 9 using the attributes set by marshal values.

So, the 8 arrangement of the managed structure has 7 no impact on the arranged of the native 6 structure. Only the attributes affect the 5 arrangement of native structure.

If fields 4 setup with marshal attributes were stored 3 in managed data the same way as native data, then 2 there would be no point in Marshal.StructureToPtr, you'd 1 simply byte-copy the data over.

Score: 1

If you're going to interop with C/C++, I 13 would always be specific with the StructLayout. Instead 12 of Sequential, I would go with Explicit, and 11 specify each position with FieldOffset. In 10 addition, add your Pack variable.

[StructLayout(LayoutKind.Explicit, Pack=1, CharSet=CharSet.Unicode)]
public struct Inner
{
    [FieldOffset(0)]
    public byte First;
    [FieldOffset(1)]
    public double NotFirst;
    [FieldOffset(9)]
    public DateTime WTF;
}

It sounds 9 like DateTime can't be Marshaled anyhow, only 8 to a string (bingle Marshal DateTime).

The 7 Pack variable is especially important in 6 C++ code that might be compiled on different 5 systems that have different word sizes.

I 4 would also ignore the addresses that can 3 be seen when using unsafe code. It doesn't 2 really matter what the compiler does as 1 long as the Marshaling is correct.

More Related questions