.NET behaviour of LayoutKind.explicit for a field which is itself a struct

寵の児 提交于 2019-12-24 02:17:05

问题


Question

I tried building a struct (SA) using [StructLayout(LayoutKind.Explicit)], which had a field which is another struct (SB).

First: I was surprised I was allowed to declare that other struct without [StructLayout(LayoutKind.Explicit)], whereas in SA, all fields must have [FieldOffset(0)], or the compiler will shout. It doesn't make much sense.

  • Is this a loophole in the compiler's warnings/errors ?

Second: it seems that all reference (object) fields in SB are moved to the front of SB.

  • Is this behaviour described anywhere?
  • Is it implementation-dependant?
  • Is it defined anywhere that it is implementation-dependant? :)

Note: I'm not intending to use this in production code. I ask this question mainly out of curiosity.

Experimentation

// No object fields in SB
// Gives the following layout (deduced from experimentation with the C# debugger):

// | f0 | f4 and i | f8 and j | f12 and k | f16 |

[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] int f4;
    [FieldOffset(8)] int f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; int k; }

// One object field in SB
// Gives the following layout:

// | f0 | f4 and o1 | f8 and i | f12 and j | f16 and k |

// If I add an `object` field after `j` in `SB`, i *have* to convert
// `f4` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.

[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] object f4;
    [FieldOffset(8)] int f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; int k; }

// Two `object` fields in `SB`
// Gives the following layout:

// | f0 | f4 and o1 | f8 and o2 | f12 and i | f16 and j | k |

// If I add another `object` field after the first one in `SB`, i *have* to convert
// `f8` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.

[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] object f4;
    [FieldOffset(8)] object f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; object o2; int k; }

回答1:


Is this a loophole in the compiler's warnings/errors ?

No, nothing wrong with it. Fields are allowed to overlap, this is why LayoutKind.Explicit exists in the first place. It allows declaring the equivalent of a union in unmanaged code, not otherwise supported in C#. You cannot suddenly stop using [FieldOffset] in a structure declaration, the compiler insist that you use it on all members of the struct. Not technically necessary but a simple requirement that avoids wrong assumptions.

it seems that all reference (object) fields in SB are moved

Yes, this is normal. The CLR lays out objects in an undocumented and undiscoverable way. The exact rules it uses are not documented and subject to change. It also won't repeat for different jitters. Layout doesn't become predictable until the object is marshaled, Marshal.StructureToPtr() call or implicitly by the pinvoke marshaller. Which is the only time the exact layout matters. I wrote about the rationale for this behavior in this answer.




回答2:


The answer to the first question is no, there's no loophole or bug in the compiler's error reporting. If you start doing explicit layout, the compiler is going to assume that you know what you're doing (within limits--see below). You told it to overlay one structure on top of another. The compiler doesn't (and shouldn't) care that the structure you're overlaying isn't also explicitly laid out.

If the compiler did care, then you wouldn't be able to overlay any type that wasn't explicitly laid out, meaning that you couldn't do a union in the general case. Consider, for example, trying to overlay a DateTime and a long:

[StructLayout(LayoutKind.Explicit)]
struct MyUnion
{
    [FieldOffset(0)]
    public bool IsDate;
    [FieldOffset(1)]
    public DateTime dt;
    [FieldOffset(1)]
    public long counter;
}

That wouldn't compile unless DateTime were explicitly laid out. Probably not what you want.

As far as putting reference types in explicitly laid out structures goes, your results are going to be ... probably not what you expected. Consider, for example, this simple bit:

struct MyUnion
{
    [FieldOffset(0)]
    public object o1;
    [FieldOffset(0)]
    public SomeRefType o2;
}

That violates type safety in a big way. If it compiles (which it very well might), it will die with a TypeLoadException when you try to use it.

The compiler will prevent you from violating type safety where possible. I don't know if the compiler knows how to process those attributes and layout the structure, or if it just passes the layout information to the runtime through the generated MSIL. Probably the latter, considering your second example, where the compiler allowed a particular layout but the runtime bombed with a TypeLoadException.

A Google search on [structlayout.explicit reference types] reveals some interesting discussions. See Overlaying several CLR reference fields with each other in explicit struct?, for example.



来源:https://stackoverflow.com/questions/15392054/net-behaviour-of-layoutkind-explicit-for-a-field-which-is-itself-a-struct

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!