}
Первым шагом при размещении локальных переменных с помощью CIL является применение директивы
.locals
в паре с атрибутом
init
. Каждая переменная идентифицируется своим типом данных и необязательным именем. После определения локальных переменных значения загружаются в стек (с использованием различных кодов операций загрузки) и сохраняются в этих локальных переменных (с помощью кодов операций сохранения).
Отображение параметров на локальные переменные в CIL
Вы уже видели, каким образом объявляются локальные переменные в CIL с применением директивы
.locals init
; однако осталось еще взглянуть на то, как входные параметры отображаются на локальные переменные. Рассмотрим показанный ниже статический метод С#:
public static int Add(int a, int b)
{
return a + b;
}
Такой с виду невинный метод требует немалого объема кодирования на языке CIL. Во-первых, входные аргументы (
а
и
b
) должны быть помещены в виртуальный стек выполнения с использованием кода операции
ldarg
(load argument — загрузить аргумент). Во-вторых, с помощью кода операции
add
из стека будут извлечены следующие два значения и просуммированы с сохранением результата обратно в стек. В-третьих, сумма будет извлечена из стека и возвращена вызывающему коду посредством кода операции
ret
. Дизассемблировав этот метод C# с применением
ildasm.exe
, вы обнаружите множество дополнительных лексем, которые были внедрены в процессе компиляции, но основная часть кода CIL довольно проста:
.method public hidebysig static int32 Add(int32 a,
int32 b) cil managed
{
.maxstack 2
ldarg.0 // Загрузить а в стек.
ldarg.1 // Загрузить b в стек.
add // Сложить оба значения.
ret
}
Скрытая ссылка this
Обратите внимание, что ссылка на два входных аргумента (
а
и
b
) в коде CIL производится с использованием их индексных позиций (
0
и
1
), т.к. индексация в виртуальном стеке выполнения начинается с нуля.
Во время исследования или написания кода CIL нужно помнить о том, что каждый нестатический метод, принимающий входные аргументы, автоматически получает неявный дополнительный параметр, который представляет собой ссылку на текущий объект (подобно ключевому слову
this
в С#). Скажем, если бы метод
Add()
был определен как
нестатический:
<b>// Больше не является статическим!</b>
public int Add(int a, int b)
{
return a + b;
}
то входные аргументы
а
и
b
загружались бы с применением кодов операций
ldarg.1
и
ldarg.2
(а не ожидаемых
ldarg.0
и
ldarg.1
). Причина в том, что ячейка 0 содержит неявную ссылку
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
и
do
, каждое из которых имеет специальное представление в CIL. В качестве примера рассмотрим следующий классический цикл
for:
public static void CountToTen()
{
for(int i = 0; i < 10; i++)
{
}
}
Вспомните, что для управления прекращением потока выполнения, когда удовлетворено некоторое условие, используются коды операций
br
(
br
,
bltn
т.д.). В приведенном примере указано условие, согласно которому выполнение цикла
for
должно прекращаться, когда значение локальной переменной
i
становится больше или равно 10. С каждым проходом к значению
i
добавляется 1, после чего проверяемое условие оценивается заново.
Также вспомните, что в случае применения любого кода операции 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 // Загрузить это значение в стек.