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

Почему буферы фиксированного размера имеют только примитивные типы?

Нам приходится много взаимодействовать с собственным кодом, и в этом случае гораздо быстрее использовать небезопасные структуры, которые не требуют маршалинга. Однако мы не можем этого сделать, когда структуры содержат фиксированные буферы с непримитивными типами. Почему из компилятора С# требуется, чтобы фиксированные буферы были только примитивными типами? Почему буфер фиксированного размера не может быть создан из структуры, например:

[StructLayout(LayoutKind.Sequential)]
struct SomeType
{
  int Number1;
  int Number2;
}
4b9b3361

Ответ 1

Буферы фиксированного размера в С# реализованы с помощью функции CLI, называемой "непрозрачными классами". Раздел I.12.1.6.3 Ecma-335 описывает их:

Некоторые языки предоставляют многобайтовые структуры данных, содержимое которых напрямую управляется адресные арифметические и косвенные операции. Для поддержки этой функции CLI позволяет использовать типы значений для создания с указанным размером, но не информация об их данных. Экземпляры эти "непрозрачные классы" обрабатываются точно так же, как и экземпляры любого другого класса, но команды ldfld, stfld, ldflda, ldsfld и stsfld не должны использоваться для доступа к их содержимому.

"Никакая информация об их членах данных" и "ldfld/stfld не должна использоваться" - это руб. Второе правило помещает кибош в структуры, вам нужно ldfld и stfld для доступа к своим членам. Компилятор С# не может предоставить альтернативу, макет структуры представляет собой деталь выполнения выполнения. Также причина, по которой вы не можете использовать оператор sizeof в структуре. Десятичные и Nullable < > отсутствуют, поскольку они также являются структурами. IntPtr отсутствует, потому что его размер зависит от битности процесса, что затрудняет компилятору С# создание адреса для кода операции ldind/stind, используемого для доступа к буферу. Ссылки ссылочных типов отсутствуют, потому что GC должен быть в состоянии найти их назад и не может по 1-му правилу. Типы Enum имеют переменный размер, который зависит от их базового типа; звучит как разрешимая проблема, не совсем уверен, почему они пропустили ее.

Который просто оставляет те, о которых говорит спецификация языка С#: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double или bool. Просто простые типы с четко определенным размером.

Ответ 2

Что такое фиксированный буфер?

Из MSDN:

В С# вы можете использовать фиксированный оператор для создания буфера с массивом фиксированного размера в структуре данных. Это полезно, когда вы работаете с существующим кодом, например, с кодом, написанным на других языках, с уже существующими DLL или COM-проектами. Фиксированный массив может принимать любые атрибуты или модификаторы, которые разрешены для регулярных членов структуры. Единственное ограничение состоит в том, что тип массива должен быть bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float или double.

Я просто буду цитировать г-на Ханса Пассана в отношении того, почему фиксированный буфер ДОЛЖЕН быть unsafe. Вы можете увидеть Почему буферы с фиксированным размером (массивы) должны быть небезопасными? для получения дополнительной информации.

Потому что "фиксированный буфер" не является реальным массивом. Это настраиваемый тип значения, о единственном способе для генерации одного на языке С#, который я знаю. Нет возможности для CLR, чтобы убедиться, что индексирование массива выполняется безопасным способом. Код также не поддается проверке. Самая графическая демонстрация это:

using System;

class Program {
    static unsafe void Main(string[] args) {
        var buf = new Buffer72();
        Console.WriteLine(buf.bs[8]);
        Console.ReadLine();
    }
}
public struct Buffer72 {
    public unsafe fixed byte bs[7];
}

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

Да, это очень опасно.

Почему не может фиксированный буфер содержать непримитивные типы данных?

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

Я собираюсь пойти с "добавленными сложностями в компилятор". Компилятор должен будет проверить, что для структуры, применяемой к перечислимым элементам, не применялась специальная функциональность .NET. Например, дженерики, реализация интерфейса, еще более глубокие свойства непримитивных массивов и т.д. Несомненно, время выполнения также будет иметь некоторые проблемы взаимодействия с такими вещами.

И Ибаса:

"Но это уже сделано компилятором". Только отчасти. Компилятор может выполнить проверки, чтобы убедиться, что тип управляется, но который не заботится о генерации кода для чтения/записи структур на фиксированные буферы. Это можно сделать (там ничего не останавливается на уровне CIL), он просто не реализован на С#.

Наконец, Мехрдад:

Я думаю, что это буквально потому, что они не хотят, чтобы вы использовали буферы фиксированного размера (потому что они хотят, чтобы вы использовали управляемый код). Слишком просто вмешаться в собственный код, вы вряд ли будете использовать .NET для всего, и они хотят как можно больше продвигать управляемый код.

Ответ кажется громким, "он просто не реализован".

Почему он не реализован?

Моя догадка заключается в том, что стоимость и время реализации просто не стоят им. Разработчики предпочли бы продвигать управляемый код по неуправляемому коду. Это возможно сделать в будущей версии С#, но в текущем CLR не хватает большой сложности.

Альтернативой может быть проблема безопасности. Поскольку эти фиксированные буферы чрезвычайно уязвимы для всех видов проблем и рисков безопасности, если они будут плохо реализованы в вашем коде, я могу понять, почему их использование будет препятствовать управлению управляемым кодом на С#. Зачем вкладывать много работы в то, что вы хотели бы препятствовать использованию?

Ответ 3

Я понимаю вашу точку зрения... с другой стороны, я полагаю, что это может быть какая-то передовая совместимость, зарезервированная Microsoft. Ваш код скомпилирован в MSIL, и это бизнес конкретной платформы .NET Framework и ОС для компоновки его в памяти.

Я могу себе представить, что это может принести новый процессор от Intel, который потребует переменные макета до каждых 8 байтов, чтобы получить оптимальную производительность. В этом случае в будущем будет необходимо, в какой-то будущей .NET Framework 6 и в будущей Windows 9 для компоновки этих структур по-разному. В этом случае ваш примерный код будет давлением для Microsoft, чтобы не менять макет памяти в будущем, а не ускорять платформу .NET до современного HW.

Это только предположение...

Вы пытались установить FieldOffset? См. Объединение С++ в С#