// Извлечь текущее значение и сохранить его в локальной переменной [0].
stloc.0
// Загрузить константу типа i4 (сокращение для int32) со значением 33.
ldc.i4.s 33
// Извлечь текущее значение и сохранить его в локальной переменной [1].
stloc.1
// Создать новый объект и поместить его в стек.
newobj instance void [mscorlib]System.Object::.ctor()
// Извлечь текущее значение и сохранить его в локальной переменной [2].
stloc.2
ret
}
Первым шагом при размещении локальных переменных с помощью 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() был определен как // Больше не является статическим!
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 // 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++)
{
}
}