Язык программирования C#9 и платформа .NET5
}Первым шагом при размещении локальных переменных с помощью CIL является применение директивы
в паре с атрибутом.locals. Каждая переменная идентифицируется своим типом данных и необязательным именем. После определения локальных переменных значения загружаются в стек (с использованием различных кодов операций загрузки) и сохраняются в этих локальных переменных (с помощью кодов операций сохранения).initОтображение параметров на локальные переменные в CIL
Вы уже видели, каким образом объявляются локальные переменные в CIL с применением директивы
; однако осталось еще взглянуть на то, как входные параметры отображаются на локальные переменные. Рассмотрим показанный ниже статический метод С#:.locals initpublic static int Add(int a, int b){return a + b;}Такой с виду невинный метод требует немалого объема кодирования на языке CIL. Во-первых, входные аргументы (
иа) должны быть помещены в виртуальный стек выполнения с использованием кода операцииb(load argument — загрузить аргумент). Во-вторых, с помощью кода операцииldargиз стека будут извлечены следующие два значения и просуммированы с сохранением результата обратно в стек. В-третьих, сумма будет извлечена из стека и возвращена вызывающему коду посредством кода операцииadd. Дизассемблировав этот метод C# с применениемret, вы обнаружите множество дополнительных лексем, которые были внедрены в процессе компиляции, но основная часть кода CIL довольно проста:ildasm.exe.method public hidebysig static int32 Add(int32 a,int32 b) cil managed{.maxstack 2ldarg.0 // Загрузить а в стек.ldarg.1 // Загрузить b в стек.add // Сложить оба значения.ret}Скрытая ссылка this
Обратите внимание, что ссылка на два входных аргумента (
иа) в коде CIL производится с использованием их индексных позиций (bи0), т.к. индексация в виртуальном стеке выполнения начинается с нуля.1Во время исследования или написания кода CIL нужно помнить о том, что каждый нестатический метод, принимающий входные аргументы, автоматически получает неявный дополнительный параметр, который представляет собой ссылку на текущий объект (подобно ключевому слову
в С#). Скажем, если бы методthisбыл определен как нестатический:Add()<b>// Больше не является статическим!</b>public int Add(int a, int b){return a + b;}то входные аргументы
иазагружались бы с применением кодов операцийbиldarg.1(а не ожидаемыхldarg.2иldarg.0). Причина в том, что ячейка 0 содержит неявную ссылкуldarg.1. Взгляните на следующий псевдокод:this<b>// Это ТОЛЬКО псевдокод!</b>.method public hidebysig static int32 AddTwoIntParams(MyClass_HiddenThisPointer this, int32 a, int32 b) cil managed{ldarg.0 // Load MyClass_HiddenThisPointer onto the stack.ldarg.1 // Load "a" onto the stack.ldarg.2 // Load "b" onto the stack....}Представление итерационных конструкций в CIL
Итерационные конструкции в языке программирования C# реализуются посредством ключевых слов
,for,foreachиwhile, каждое из которых имеет специальное представление в CIL. В качестве примера рассмотрим следующий классический циклdofor:public static void CountToTen(){for(int i = 0; i < 10; i++){}}Вспомните, что для управления прекращением потока выполнения, когда удовлетворено некоторое условие, используются коды операций
(br,brт.д.). В приведенном примере указано условие, согласно которому выполнение циклаbltnдолжно прекращаться, когда значение локальной переменнойforстановится больше или равно 10. С каждым проходом к значениюiдобавляется 1, после чего проверяемое условие оценивается заново.iТакже вспомните, что в случае применения любого кода операции CIL, предназначенного для ветвления, должна быть определена специфичная метка кода (или две), обозначающая место, куда будет произведен переход при истинном результате оценки условия. С учетом всего сказанного рассмотрим показанный ниже (отредактированный) код CIL, который сгенерирован утилитой
(вместе с автоматически созданными метками):ildasm.exe.method public hidebysig static void CountToTen() cil managed{.maxstack 2.locals init (int32 V_0, bool V_1)IL_0000: ldc.i4.0 // Загрузить это значение в стек.