Почему sizeof структуры небезопасен

В MSDN четко указано

Для всех других типов, включая структуры, оператор sizeof может использоваться в небезопасных кодовых блоках.

Подробнее о С# Language Specification:

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

Однако как CLR обрабатывает следующие структуры:

[StructLayout(LayoutKind.Explicit, Size = 1, Pack = 1)]
public struct MyStruct
{
    [FieldOffset(0)] public byte aByte;
}

public struct MyEmptyStruct { }

В MyStruct мы явно применяем макет, размер и как его упаковать с помощью атрибута StructLayout. Предполагается, что эта структура имеет размер 1 байт в памяти.

С другой стороны, MyEmptyStruct пуст, мы можем предположить, что размер в памяти будет 0 байтов - даже если такая структура, скорее всего, не будет использоваться, все же интересна.

При попытке вычислить размер структуры тезисов с помощью sizeof(MyStruct) и sizeof(MyEmptyStruct) компилятор выдает следующую ошибку:

'*' не имеет предопределенного размера, поэтому sizeof может только использоваться в небезопасном контексте

Я хотел бы знать , почему, используя sizeof в этом контексте, считается unsafe. Вопрос не в том, чтобы запрашивать обходные пути или правильный способ вычислить размер структуры, а скорее сосредоточиться на причинах.

Ответ 1

Я хотел бы знать, почему использование sizeof в этом контексте считается небезопасным.

Комментарий Мэтью Уотсона поражает гвоздь на голове. Что вы собираетесь делать с этой информацией в безопасном коде? Это не полезно ни для чего (*). Он не говорит вам, сколько неуправляемых байтов вам нужно выделить маршалу; что Marshal.SizeOf. Это полезно только для арифметики указателей, так почему это должно быть в безопасном подмножестве?


(*) ОК, чтобы быть справедливым, есть несколько нечетных аргументов в отношении углов для безопасного sizeof, которые могут принимать структуры, которые содержат управляемые типы. Предположим, например, что у вас есть общий класс коллекций, который собирается выделить кучу массивов и хотел бы гарантировать, что эти массивы не будут перемещены в кучу больших объектов; если бы вы могли взять размер структуры, содержащей управляемые объекты, то вы могли бы легко написать этот код, и ему не понадобилась бы арифметика указателей. Но факт остается фактом: sizeof был разработан специально для арифметики указателя, а не для того, чтобы вы могли выполнить конечный результат эвристики коллекции мусора для массивов.

Ответ 2

Множество неправильных предположений в вопросе, я просто буду обращаться к ним один за другим:

в MyStruct мы явно используем макет

Вы этого не сделали. Атрибут [StructLayout] эффективен только тогда, когда значение структуры маршалируется. Marshal.StructureToPtr(), также используемый маркеллером pinvoke. Только тогда вы получаете гарантию, что маршалированное значение имеет запрошенный макет. CLR оставляет за собой право компоновать структуру по своему усмотрению. Он будет выравнивать элементы структуры, чтобы код, который использует структуру, был как можно быстрее, вставляя при необходимости пустые байты. И если такие байты заполнения оставляют достаточно места, тогда он даже поменяет членов, чтобы получить меньший макет. Это совершенно невозможно обнаружить, за исключением использования отладчика для просмотра машинного кода, который обращается к членам структуры. Некоторые свойства [StructLayout] влияют на макет, LayoutKind.Explicit фактически поддерживает объявления союзов. Точные детали алгоритма отображения недокументированы, могут быть изменены и сильно зависят от архитектуры целевой машины.

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

Это не так, фактическая структура может быть меньше объявленной структуры. Возможно, заменив элемент на прокладку.

Эта структура должна иметь размер 1 байт в памяти.

Это очень редко. Локальные переменные также выровнены в памяти, на 4 байта на 32-битном процессоре и 8 байтах в 64-разрядном процессоре. Если структура не хранится в массиве, она фактически занимает 4 или 8 байтов в стеке или внутри объекта в куче. Это выравнивание важно по той же причине, что и выравнивание элементов.

MyEmptyStruct пуст, мы можем предположить, что размер в памяти будет 0 байтов

У переменной всегда будет не менее 1 байт, даже если структура пуста. Это позволяет избежать неоднозначностей, таких как наличие непустого массива, который принимает нулевые байты. Также правило на других языках, например С++.

почему использование sizeof в этом контексте считается небезопасным

Чтобы быть ясным, использование sizeof для примитивных типов значений не требует небезопасности с .NET 2. Но для структур существует определенная возможность того, что sizeof() может использоваться для прямого обращения к памяти, добавив его к IntPtr, например, Со значительным риском, что использование sizeof() было неправильным выбором, и должен был быть Marshal.SizeOf(). Я бы предположил, что практичность использования sizeof() в структурах настолько мала, что структура всегда должна быть малой, а вероятность взлома IntPtrs не так высока, что они оставили ее небезопасной.