Подавить генерацию флага localsinit
.
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Включает предлагаемые изменения в спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия отражены в соответствующей заметке заседания по разработке языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Сводка
Разрешить подавление эмиссии флага localsinit
с помощью атрибута SkipLocalsInitAttribute
.
Мотивация
Фон
Согласно спецификации CLR, локальные переменные, которые не содержат ссылок, не инициализируются до какого-либо определенного значения виртуальной машиной или JIT. Чтение из таких переменных без инициализации является типобезопасным, но в противном случае поведение не определено и зависит от реализации. Обычно неинициализированные локальные переменные содержат произвольные значения, оставшиеся в памяти, занятой кадром стека. Это может привести к недетерминированному поведению и ошибкам, которые трудно воспроизвести.
Существует два способа "назначить" локальную переменную:
- сохранением значения или
- При указании флага
localsinit
все, что выделено из локального пула памяти, будет инициализировано нулями. Примечание: это включает как локальные переменные, так и данныеstackalloc
.
Использование неинициализированных данных не рекомендуется и не допускается в проверяемом коде. Хотя можно было бы доказать это с помощью анализа потока, допускается, что алгоритм проверки будет консервативным и просто требовать, чтобы localsinit
было установлено.
Исторически компилятор C# генерировал флаг localsinit
для всех методов, объявляющих локальные переменные.
Хотя C# использует анализ строго определённого назначения, который является более строгим, чем того требует спецификация CLR (C# также необходимо учитывать область локальных переменных), не гарантируется, что полученный код будет формально проверяемым.
- Правила CLR и C# могут не согласиться, допускается ли передача локальной переменной как аргумента
out
use
. - Правила CLR и C# могут не совпадать в вопросах обработки условных ветвей, когда условия известны (распространение констант).
- CLR также может просто требовать
localinits
, поскольку это разрешено.
Проблема
В высокопроизводительном приложении затраты на принудительную инициализацию с нулевыми значениями могут быть заметны. Особенно заметно при использовании stackalloc
.
В некоторых случаях JIT может опустить начальную инициализацию локальных переменных нулями, если такая инициализация "убита" последующими назначениями. Не все JIT это делают, и такая оптимизация имеет свои ограничения. Это не помогает с stackalloc
.
Чтобы показать, что проблема реальна, существует известная ошибка, при которой метод, не содержащий никаких IL
локальных переменных, не будет иметь флага localsinit
. Ошибка уже эксплуатируется пользователями, помещая stackalloc
в такие методы намеренно, чтобы избежать затрат на инициализацию. Несмотря на то, что отсутствие IL
локальных переменных является нестабильной метрикой и может изменяться в зависимости от изменений в стратегии кодогенерации.
Ошибка должна быть исправлена, и пользователи должны получить более документированные и надежные способы подавления флага.
Подробный дизайн
Разрешить указывать System.Runtime.CompilerServices.SkipLocalsInitAttribute
как способ сообщить компилятору, чтобы он не выдавал флаг localsinit
.
Результатом этого может стать то, что локальные переменные могут не быть инициализированы значением нуля JIT, что в большинстве случаев не наблюдается в C#.
Помимо этого stackalloc
данные не будут инициализированы с нуля. Это определенно наблюдаемо, но также является наиболее мотивирующим сценарием.
Допустимые и распознанные целевые объекты атрибутов: Method
, Property
, Module
, Class
, Struct
, Interface
, Constructor
. Однако компилятору не потребуется, чтобы атрибут был определен с указанными целевыми объектами, а также не будет заботиться о том, в какой сборке определен атрибут.
Если атрибут указан в контейнере (class
, module
, содержащий метод для вложенного метода, ...), флаг влияет на все методы, содержащиеся в контейнере.
Синтезированные методы "наследуют" флаг от логического контейнера или владельца.
Флаг влияет только на стратегию генерации кода для реальных тел методов. То есть флаг не влияет на абстрактные методы и не распространяется на методы, переопределяющие и реализующие.
Это явно компонент компилятора и не компонент языка.
Аналогично опциям командной строки компилятора, элементы управления функциями определяют детали реализации конкретной стратегии генерации кода и не требуют включения в спецификацию C#.
Недостатки
Старые или другие компиляторы могут не учитывать атрибут. Игнорирование атрибута является совместимым поведением. Может лишь привести к незначительной потере производительности.
Код без флага
localinits
может вызвать сбои проверки. Пользователи, запрашивающие эту функцию, обычно не обеспокоены проверкой.Применение атрибута на более высоких уровнях, чем отдельный метод, имеет нелокальный эффект, который наблюдается при использовании
stackalloc
. Тем не менее, это самый запрошенный сценарий.
Альтернативы
Опускать флаг
localinits
, если метод объявлен в контекстеunsafe
. Это может вызвать тихое и опасное изменение поведения из детерминированного в недетерминированное в случаеstackalloc
.всегда опустить флаг
localinits
. Еще хуже, чем выше.опустить флаг
localinits
, если в тексте метода не используетсяstackalloc
. Не решает наиболее часто запрашиваемый сценарий и может сделать код непроверяемым без возможности его восстановления обратно.
Неразрешенные вопросы
- Должен ли атрибут фактически выводиться в метаданные?
Совещания по проектированию
Пока нет.
C# feature specifications