.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 staticint Add(int a , int b) {
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
}
Обратите внимание на то, что в рамках программного кода CIL для ссылок на два входных аргумента (а и b) используются их индексы позиции (индекс 0 и индекс 1, поскольку индексация в виртуальном стеке выполнения начинается с нуля).
При анализе программного кода и его создании непосредственно в CIL следует быть очень внимательным, поскольку каждый (нестатический) метод, имеющий входные аргументы, автоматически получает неявный дополнительный параметр, который является ссылкой на текущий объект (это должно вызвать аналогию с ключевым словом C# this). Поэтому, если определить метод Add(), как нестатический
// Уже не является статическим!
public int Add(int a, int b) {
return a + b;
}
то входные аргументы а и b будут загружаться с помощью ldarg.1 и ldarg.2 (а не с помощью ожидаемых ldarg.0 и ldarg.1). Причина как раз в том, что ячейка 0 будет содержать неявную ссылку this. Рассмотрите следующий псевдокод.
// Это только псевдокод!
.method public hidebysig static int32 AddTwoIntParams( MyClass_HiddenThisPointer this,int32 a, int32 b) cil managed {
ldarg.0 // Загрузка MyClass_HiddenThisPointerв стек,
ldarg.1 // Загрузка 'а' в стек.
ldarg.2 // Загрузка 'b' в стек.
…
}
Представление итерационных конструкций
Итерационные конструкции в языке программирования C# представляются с помощью ключевых слов for, foreach, while и do, каждое из которых имеет свое специальное представление в CIL. Рассмотрим классический цикл for.
public static void CountToTen() {
for (int i = 0; i ‹ 10; i++);
}
Вы можете помнить о том, что коды операций br (br, blt и т.д.) используются для управления потоком программы в зависимости от выполнения некоторого условия. В нашем примере мы задали условие, по которому должен произойти выход из цикла, когда значение локальной переменной i станет равным 10. С каждым проходом к значению i добавляется 1, после чего сразу же выполняется тестовое сравнение.
Читать дальше