Расположение типа значения .NET в памяти - PullRequest
13 голосов
/ 17 декабря 2009

У меня есть следующие типы значений .NET:

[StructLayout(LayoutKind.Sequential)]
public struct Date
{
    public UInt16 V;
}

[StructLayout(LayoutKind.Sequential)]
public struct StringPair
{
    public String A;
    public String B;
    public String C;
    public Date D;
    public double V;
}

У меня есть код, который передает указатель на тип значения неуправляемому коду, а также смещения, обнаруженные путем вызова System.Runtime.InteropServices.Marshal.OffsetOf. Неуправляемый код заполняет дату и двойные значения.

Смещения, о которых сообщается для структуры StringPair, в точности соответствуют ожиданиям: 0, 8, 16, 24, 32

У меня есть следующий код в тестовой функции:

FieldInfo[] fields = typeof(StringPair).GetFields(BindingFlags.Instance|BindingFlags.Public);

for ( int i = 0; i < fields.Length; i++ )
{
    int offset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(StringPair), fields[i].Name).ToInt32();

    Console.WriteLine(String.Format(" >> field {0} @ offset {1}", fields[i].Name, offset));
}

Который печатает именно эти смещения.

 >> field A @ offset 0
 >> field B @ offset 8
 >> field C @ offset 16
 >> field D @ offset 24
 >> field V @ offset 32

У меня есть тестовый код: foreach (пара StringPair в парах) { Дата d = pair.D; double v = пара. V; ...

С которым связан следующий ассемблер в отладчике:

               Date d = pair.D;
0000035d  lea         rax,[rbp+20h] 
00000361  add         rax,20h 
00000367  mov         ax,word ptr [rax] 
0000036a  mov         word ptr [rbp+000000A8h],ax 
00000371  movzx       eax,word ptr [rbp+000000A8h] 
00000378  mov         word ptr [rbp+48h],ax 

                double v = pair.V;
0000037c  movsd       xmm0,mmword ptr [rbp+38h] 
00000381  movsd       mmword ptr [rbp+50h],xmm0 

Загружается поле D со смещением 32 (0x20) и поле V со смещением 24 (0x38-0x20). JIT изменил порядок вокруг. Отладчик Visual Studio также показывает этот перевернутый порядок.

Почему !? Я вырывал свои волосы, пытаясь понять, где моя логика идет не так. Если я поменяю местами порядок D и V в структуре, то все будет работать, но этот код должен уметь работать с архитектурой плагинов, в которой другие разработчики определили структуру, и нельзя ожидать, что они запомнят загадочные правила компоновки.

Ответы [ 3 ]

14 голосов
/ 17 декабря 2009

Если вам нужен явный макет ... используйте явный макет ...

[StructLayout(LayoutKind.Explicit)]
public struct StringPair
{
    [FieldOffset(0)] public String A;
    [FieldOffset(8)] public String B;
    [FieldOffset(16)] public String C;
    [FieldOffset(24)] public Date D;
    [FieldOffset(32)] public double V;
}
12 голосов
/ 17 декабря 2009

Информация, которую вы получаете из класса Marshal, актуальна только в том случае, если тип действительно маршалируется. Структура внутренней памяти управляемой структуры не может быть обнаружена никакими документированными средствами, кроме как, возможно, заглядывать в код сборки.

Это означает, что CLR может свободно реорганизовать структуру конструкции и оптимизировать упаковку. Меняя местами поля D и V, ваша структура становится меньше из-за требований выравнивания в два раза. Это экономит 6 байтов на вашем 64-битном компьютере.

Не уверен, почему это будет проблемой для вас, не должно быть. Рассмотрим Marshal.StructureToPtr (), чтобы выстроить структуру так, как вы хотите.

1 голос
/ 13 марта 2011

Две вещи:

  • StructLayout(Sequential) не гарантирует упаковку. Возможно, вы захотите использовать Pack=1, в противном случае 32- и 64-битные платформы могут отличаться.

  • и строка является ссылкой, а не указателем. Если длина строки всегда фиксирована, вы можете использовать фиксированные массивы символов:

    public struct MyArray // This code must appear in an unsafe block
    {
        public fixed char pathName[128];
    }
    
...