2013-03-13 4 views
2

Question

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

Первый: Я был удивлен, мне было позволено заявить, что другой без [StructLayout(LayoutKind.Explicit)]-структуру, в то время как в SA, все поля MUST имеют [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. Это единственный раз, когда важна точная компоновка. Я писал об обосновании такого поведения в this answer.

+0

Если я не ошибаюсь, у структуры также есть «макетированный макет», когда мы бросаем необработанный небезопасный указатель на указатель на эту структуру, не так ли? Скажем, в 'unsafe {SA * sa = (SA *) 0x1234;/* Использовать sa здесь * /} ',' sa' будет иметь тот же макет, что и при его сортировке, не так ли? –

+0

Нет, это не маршалинг. Компилятор не позволит вам это сделать, структура не будет ослабляться. Попробуй. –

+0

Действительно, он работает с structs, содержащим только int, но не при добавлении поля объекта. Однако вопрос сохраняется для структур, которые содержат только 'int' и такие, как первый, который я использовал. –

1

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

Если компилятор сделал уходом, то вы не смогли бы наложить какой-либо тип, который не был явно выложен, что означает, что вы не можете сделать объединение в общем случае. Рассмотрим, например, пытаясь наложить 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 reference types] показывает некоторые интересные обсуждения. См., Например, Overlaying several CLR reference fields with each other in explicit struct?.

+1

Не существует исключения TypeLoadException для структуры MyUnion. Он будет расти только тогда, когда вы перекрываете значение типа значения с объектом ссылочного типа. Как и у ОП. Попробуй. –

+0

Это похоже ... опасно. Или, возможно, нет. Хмммм. , , Я думаю, что было бы безопасно наложить два типа ссылок. Исключение Runtime произойдет, если я попытаюсь разыграть «o2» после установки «o1» на то, что не является «SomeRefType». –

+0

Это кажется мне опасным: вы можете наложить два массива разных типов. Это позволяет вам получать доступ к памяти за пределами области меньшего массива. –