call void [mscorlib]System.Consolr::WriteLine(string)
ret
}
Объявление локальных переменных
Давайте выясним, как объявляются локальные переменные. Предположим, что мы должны построить в терминах CIL метод MyLocalVariables, не имеющий никаких аргументов и возвращающий void. В этом методе мы должны определить три локальные переменные типов System.String, System.Int32 и System.Object. В C# соответствующий программный код мог бы выглядеть так, как показано ниже (напомним, что локальные переменные не получают значения по умолчанию, поэтому им перед использованием необходимо присвоить начальные значения).
public static void MyLocalVariables {
string myStr = "CIL me dude…";
int myInt = 33;
object myObj = new object;
}
Если создавать MyLocalVariables непосредственно в CIL, можно было бы написать следующее,
.method public hidebysig static void MyLocalVariables cil managed {
.maxstack 6
// Определение трех локальных переданных.
.locals init ([0] string myStr, [1]int32 myInt, [2]object myObj)
// Загрузка строки в виртуальный стек выполнения.
ldstr "CIL me dude…"
// Извлечение текущего значения и сохранение его
// в локальной переменной [0].
stloc.0
// Загрузка константы типа 'i4'
// (сокращение для int32) со значением 33.
ldc.i4 33
// Извлечение текущего значения и сохранение его
// в локальной переменной [1].
stloc.1
// Создание нового объекта и помещение его в стек.
newobj instance void [mscorlib]System.Object::.ctor
// Извлечение текущего значения и сохранение его
// в локальной переменной [2].
stloc.2
ret
}
Как видите, в CIL при размещении локальных переменных сначала используется директива .locals с атрибутом init. При этом в скобках каждая переменная связывается со своим числовым индексом (здесь это [0], [1] и [2]). Каждый индекс идентифицируется типом данных и (необязательно) именем переменной. После определения локальных переменных соответствующее значение загружается в стек (с помощью подходящих кодов операций, связанных с загрузкой) и запоминается в локальной переменной (с помощью подходящих кодов операций для сохранения значений).
Связывание параметров с локальными переменными
Вы только что видели, как в CIL с помощью .local init объявляются локальные переменные, однако нужно еще выяснить, как передать отступающие параметры локальным методом. Рассмотрим следующий статический метод C#.
public static int Add(int a
return a + b;
}
Этот внешне "невинный" метод в терминах CIL существенно более "многословен". Во-первых, поступающие аргументы (а и b) следует поместить в виртуальный стек выполнение с помощью кода операций ldarg (загрузка аргумента). Затем используется код операции add, чтобы извлечь два значения из стека, найти сумму и снова сохранить значение в стеке. Наконец, эта сумма извлекается из стена и возвращается вызывающей стороне с помощью кода операции ret. Если дизассемблировать указанный метод C# с помощью ildasm.exe, вы обнаружите, что компилятор csc.exe добавляет множество дополнительных лексем, хотя сущность CIL-кода оказывается исключительно простой.
.method public hidebysig static int32 Add(int32 a, int32 b) cil managed {
.maxstack 2
ldarg.0 // Загрузка 'a' в стек,
ldarg.1 // Загрузка 'b' стек,
add // Сложение этих значений.
ret
}
Скрытая ссылка this
Обратите внимание на то, что в рамках программного кода CIL для ссылок на два входных аргумента (а и b) используются их индексы позиции (индекс 0 и индекс 1, поскольку индексация в виртуальном стеке выполнения начинается с нуля).
При анализе программного кода и его создании непосредственно в CIL следует быть очень внимательным, поскольку каждый (нестатический) метод, имеющий входные аргументы, автоматически получает неявный дополнительный параметр, который является ссылкой на текущий объект (это должно вызвать аналогию с ключевым словом C# this). Поэтому, если определить метод Add, как нестатический