Подтвердить что ты не робот

LayoutKind.Sequential не соблюдается, когда подструктура имеет LayoutKind.Explicit

При запуске этого кода:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace StructLayoutTest
{
    class Program
    {
        unsafe static void Main()
        {
            Console.WriteLine(IntPtr.Size);
            Console.WriteLine();


            Sequential s = new Sequential();
            s.A = 2;
            s.B = 3;
            s.Bool = true;
            s.Long = 6;
            s.C.Int32a = 4;
            s.C.Int32b = 5;

            int* ptr = (int*)&s;
            Console.WriteLine(ptr[0]);
            Console.WriteLine(ptr[1]);
            Console.WriteLine(ptr[2]);
            Console.WriteLine(ptr[3]);
            Console.WriteLine(ptr[4]);
            Console.WriteLine(ptr[5]);
            Console.WriteLine(ptr[6]);
            Console.WriteLine(ptr[7]);  //NB!


            Console.WriteLine("Press any key");
            Console.ReadKey();
        }

        [StructLayout(LayoutKind.Explicit)]
        struct Explicit
        {
            [FieldOffset(0)]
            public int Int32a;
            [FieldOffset(4)]
            public int Int32b;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        struct Sequential
        {
            public int A;
            public int B;
            public bool Bool;
            public long Long;
            public Explicit C;
        }
    }
}

Я ожидаю, что этот вывод ОБА на x86 и x64:
4 или 8 (в зависимости от x86 или x64)

2
3
1
6
0
4
5
мусора

Что я получаю вместо этого на x86:
4

6
0
2
3
1
4
5
мусора

Что я получаю вместо этого на x64:
8

6
0
2
3
1
0
4
5

Больше:
- проблема исчезает, когда я удаляю атрибуты LayoutKind.Explicit и FieldOffset.
- Проблема удаляется, когда я удаляю поле Bool.
- Проблема удаляется, когда я удаляю длинное поле.
- Обратите внимание: на x64 кажется, что параметр атрибута Pack = 4 тоже игнорируется?

Это относится к .Net3.5, а также .Net4.0

Мой вопрос: что мне не хватает? Или это ошибка?
Я нашел аналогичный вопрос:
Почему LayoutKind.Sequential работает по-другому, если структура содержит поле DateTime?
Но в моем случае макет изменяется даже тогда, когда изменяется атрибут подструктуры, без каких-либо изменений в типах данных. Так что это не похоже на оптимизацию. Кроме того, я хотел бы отметить, что другой вопрос по-прежнему остается без ответа. В этом другом вопросе упоминается, что макет соблюдается при использовании Marshalling. Я не тестировал это сам, но мне интересно, почему макет не соблюдается для небезопасного кода, так как все соответствующие атрибуты, похоже, на месте? В документации упоминается где-то, что эти атрибуты игнорируются, если не сделано Marshalling? Зачем? Учитывая это, могу ли я даже ожидать LayoutKind.Explicit для надежной работы для небезопасного кода? Кроме того, в документации упоминается мотив сохранения структур с ожидаемым макетом:

Чтобы уменьшить проблемы, связанные с компоновкой, связанные с параметром Auto, компиляторы С#, Visual Basic и С++ задают последовательный макет для типов значений.


Но этот мотив, по-видимому, не относится к небезопасному коду?

4b9b3361

Ответ 1

Из статьи библиотеки MSDN для перечисления LayoutKind:

Точная позиция каждого члена объекта в неуправляемой памяти контролируется явно, в зависимости от значения поля StructLayoutAttribute.Pack. Каждый член должен использовать FieldOffsetAttribute, чтобы указать положение этого поля в типе.

Соответствующая фраза выделена, этого не происходит в этой программе, указатель по-прежнему очень сильно разыменовывает управляемую память.

И да, то, что вы видите, идентично тому, что происходит, когда структура содержит член типа DateTime, типа, к которому применено [StructLayout (LayoutKind.Auto)]. Код полевого маршаллера в CLR, который определяет макет, прилагает усилия для соблюдения LayoutKind.Sequential для управляемых структур. Но он быстро сдастся без визга, если столкнется с каким-либо участником, конфликтующим с этой целью. Для этого достаточно структуры, которая сама по себе не является последовательной. Вы можете видеть, что это делается в источнике SSCLI20, src/clr/vm/fieldmarshaler.cpp, ищите fDisqualifyFromManagedSequential

Который заставит это переключиться на автоматическое расположение, то же самое правило расположения, которое применялось к классам. Это переупорядочивает поля, чтобы минимизировать заполнение между членами. С чистым эффектом, что объем требуемой памяти меньше. После элемента "Bool" есть 7 байтов заполнения, неиспользуемое пространство для выравнивания элемента "Long" с адресом, кратным 8. Конечно, это расточительно, исправляя это, делая длинный элемент первым в макете,

Так что вместо явного макета с /* offset - size */annotated:

        public int A;        /*  0 - 4 */
        public int B;        /*  4 - 4 */
        public bool Bool;    /*  8 - 1 */
        // padding           /*  9 - 7 */
        public long Long;    /* 16 - 8 */
        public Explicit C;   /* 24 - 8 */
                     /* Total:  32     */ 

Это приходит с:

        public long Long;    /*  0 - 8 */
        public int A;        /*  8 - 4 */
        public int B;        /* 12 - 4 */
        public bool Bool;    /* 16 - 1 */
        // padding           /* 17 - 3 */
        public Explicit C;   /* 20 - 8 */
                     /* Total:  28     */ 

С легким сохранением 4 байта памяти. 64-битная компоновка требует дополнительного заполнения, чтобы гарантировать, что long все еще выровнен, когда он хранится в массиве. Все это в значительной степени недокументировано и может быть изменено, поэтому никогда не принимайте зависимость от структуры управляемой памяти. Только Marshal.StructureToPtr() может дать вам гарантию.