Marshal.SizeOf в структуре, содержащей guid, дает дополнительные байты

У меня есть несколько структур, которые имеют последовательный макет:

struct S1
{
  Guid id;
}

struct S2 
{
  Guid id;
  short s;
}

struct S3 
{
  Guid id;
  short s;
  short t;
}

Вызов Marshal.SizeOf над указанными типами структур, Я получил:

Size:
S1 = 16, as expected.
S2 = 20, copied an instance to a byte array, it only occupies first 18 bytes.
S3 = 20.

Мой вопрос в том, почему размер S2 равен 20, но не 18. И эта проблема возникает только тогда, когда Guid находится в структуре.

Извините, вы не можете найти полезную информацию из msdn. Я знаю, что Marshal.SizeOf дает размер пространства, который будет занимать тип в памяти, но я хочу знать, почему он заслуживает 2 дополнительных байта, чтобы размер был кратным 4.

И как я могу избежать этой "проблемы"?

Спасибо большое!

Ответ 1

Это связано с предполагаемым [StructLayout], прикрепленным к структуре, поле Пакет является важным. Он диктует, как члены структуры выровнены после того, как их маршалируются. Значение по умолчанию для Pack равно 8. В нем указано, что любой член размером менее или равным 8 байтам выравнивается. Другими словами, короткий будет выровнен по отношению к смещению, которое кратно 2, int до 4, длинному или двойному до 8.

Ключевым моментом является то, что выравнивание должно работать, когда создается массив структур. Проще продемонстрировать более простой пример:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        Console.WriteLine(Marshal.SizeOf(typeof(Example)));
        Console.ReadLine();
    }
}
struct Example {
    public int a;
    public short b;
}

Выход: 8

Элемент a заставляет увеличивать размер, добавляются два дополнительных байта заполнения, чтобы гарантировать, что int по-прежнему выравнивается со смещением, которое кратно 4, когда структура используется в массиве. Вы можете изменить результат, применив атрибут:

[StructLayout(LayoutKind.Sequential, Pack = 2)]
struct Example {
    public int a;
    public short b;
}

Выход: 6

Элемент a теперь будет неправильно выровнен, когда структура будет использоваться в массиве.

Назад к руководству, вы не можете видеть его из декларации, но он состоит из нескольких членов. Первый - это приватное поле с именем _a, и это int. Поэтому для указания Guid требуется выравнивание по 4 байт. Таким образом, необходимы 2 дополнительных байта заполнения, чтобы ваша структура правильно выравнивалась, когда она используется в массиве. Вам понадобится пакет Pack = 1 или Pack = 2 на S2, чтобы получить 18 байт.

Подробнее о структурах и о том, как они обрабатываются в .NET в этом ответе.

Ответ 2

Я не вижу, чтобы вы использовали StructLayoutAttribute в своих определениях структуры. Поэтому вы не будете знать, как они "выложены". Как вы можете сказать, что у них последовательный макет?

Некоторые источники: структуры sizeof() неизвестны. Почему? и Когда я должен явно указывать StructLayout?

Ответ 3

Я считаю, что это связано с тем, что некоторые элементы должны быть выровнены в памяти на 4 байта (или 8 байтов). Поэтому структура сделана кратной этому размеру, так что массивы структур все будут правильно выровнены. На 64-битной машине, содержащей строку, она должна быть кратна 8 байтам; Кажется, что GUID требует всего лишь 4 байта даже на 64-битных. Если структура, содержащая GUID, не была кратной 4 байтам, некоторые из GUID не были бы на 4-байтовой границе в массиве этих структур. Я не думаю, что вы можете избежать "проблемы" - почему это проблема?