.NET поведение LayoutKind.explicit для поля, которое само является структурой

Вопрос

Я пытался построить структуру (SA) с помощью [StructLayout(LayoutKind.Explicit)], который имел поле, которое является другим struct (SB).

Во-первых: я был удивлен, что мне разрешили объявить эту другую структуру без [StructLayout(LayoutKind.Explicit)]тогда как в SAвсе поля должны иметь [FieldOffset(0)], или компилятор будет кричать. Это не имеет особого смысла.

  • Это лазейка в предупреждениях / ошибках компилятора?

Второе: похоже, что все ссылки (object) поля в SB перемещены в переднюю часть SB,

  • Это поведение описано где-нибудь?
  • Это зависит от реализации?
  • Определено ли где-нибудь, что это зависит от реализации? :)

Примечание: я не собираюсь использовать это в рабочем коде. Я задаю этот вопрос в основном из любопытства.

экспериментирование

// 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; }

2 ответа

Решение

Это лазейка в предупреждениях / ошибках компилятора?

Нет, ничего плохого в этом нет. Поля могут перекрываться, поэтому LayoutKind.Explicit существует в первую очередь. Это позволяет объявлять эквивалент объединения в неуправляемом коде, который не поддерживается в C#. Вы не можете внезапно прекратить использование [FieldOffset] в объявлении структуры, компилятор настаивает на том, чтобы вы использовали его для всех членов структуры. Не технически необходимо, но простое требование, которое позволяет избежать ошибочных предположений.

кажется, что все ссылочные (объектные) поля в SB перемещены

Да, это нормально. CLR размещает объекты недокументированным и не обнаруживаемым способом. Точные правила, которые он использует, не документированы и могут быть изменены. Это также не повторится для разных дрожаний. Макет не становится предсказуемым до тех пор, пока объект не будет маршалирован, вызов Marshal.StructureToPtr() или неявно выполняется маршаллером pinvoke. Который единственный раз, когда точный макет имеет значение. Я написал об обосновании такого поведения в этом ответе.

Ответ на первый вопрос - нет, в сообщениях об ошибках компилятора нет лазейки или ошибки. Если вы начнете делать явное размещение, компилятор будет предполагать, что вы знаете, что делаете (в определенных пределах - см. Ниже). Вы сказали это наложить одну структуру поверх другой. Компилятор не заботится (и не должен заботиться) о том, что структура, которую вы накладываете, также явно не изложена.

Если бы компилятор все- таки позаботился, то вы бы не смогли наложить какой-либо тип, который не был явно указан, то есть вы не могли бы сделать объединение в общем случае. Рассмотрим, например, попытку наложения DateTime и long:

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

Это не скомпилируется, если DateTime были явно изложены. Наверное, не то, что вы хотите.

Что касается размещения ссылочных типов в явно размеченных структурах, ваши результаты будут... вероятно, не такими, как вы ожидали. Рассмотрим, например, этот простой бит:

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

Это серьезно нарушает безопасность типов. Если он компилируется (что вполне возможно), он умрет с исключением TypeLoadException, когда вы попытаетесь его использовать.

Компилятор предотвратит возможное нарушение безопасности типов. Я не знаю, знает ли компилятор, как обрабатывать эти атрибуты и макетировать структуру, или он просто передает информацию о компоновке в среду выполнения через сгенерированный MSIL. Вероятно, последний, учитывая ваш второй пример, где компилятор разрешил конкретную компоновку, но среда выполнения бомбардировалась исключением TypeLoadException.

Поиск в Google по [structlayout.explicit типам ссылок] выявляет некоторые интересные обсуждения. См. Наложение нескольких ссылочных полей CLR друг с другом в явной структуре?, например.

Другие вопросы по тегам