Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Троелсен Э. Язык программирования С# 2010 и п...docx
Скачиваний:
108
Добавлен:
21.09.2019
Размер:
6.92 Mб
Скачать

Различия между мнемоникой и кодом операции cil

Как только что объяснялось, коды операций, например ldstr, используются для реализации членов данного типа. Но в реальности лексемы (в том числе и ldstr) являются мнемониками CIL, представляющими на самом деле двоичные коды операций CIL. Чтобы пояснить различие, предположим, что у нас есть следующий метод, созданный средствами C#.

static int Add(int x, int у) {

 return х + у;

}

В терминах CIL сложение двух чисел представлено кодом операции 0X58. Аналогично для представления вычитания используется код операции 0X59, а действие, соответствующее размещению нового объекта в управляемой динамической памяти, обозначается кодом операции 0X73. С учетом сказанного должно быть ясно, что CIL-код, обрабатываемый JIT-компилятором, на самом деле является набором двоичных данных.

К счастью, для каждого двоичного кода операции CIL есть соответствующая мнемоника. Например, мнемоника add может использоваться вместо 0X58, sub – вместо 0X59, a newobj – вместо 0X73. Ввиду указанных различий между мнемониками и кодами операций, нетрудно догадаться, что декомпиляторы CIL, такие как, например, ildasm.exe, переводят двоичные коды операций компоновочного блока в соответствующую мнемонику CIL.

.method public hidebysig static int32 Add(int32 x, int32 y) cil managed {

 …

 // Лексема 'add' является более понятной мнемоникой CIL,

 // используемой для представления кода операции 0X58.

 add

 …

}

Тем, кто не сталкивается с необходимостью разработки низкоуровневого программного обеспечения .NET (например, пользовательского управляемого компилятора), обычно не приходится иметь дело непосредственно с числовыми кодами операций CIL. Поэтому практически всегда, когда программисты .NET говорят о "кодах операций CIL", они (как и я в этом тексте) имеют в виду набор более понятной мнемоники, а не лежащие в ее основе двоичные значения.

Добавление и извлечение данных: стековая природа cil

Высокоуровневые языки .NET (например, такие как C#) пытаются максимально скрыть низкоуровневые сложности. Одним из аспектов разработки .NET, который оказывается скрытым особенно хорошо, является тот факт, что CIL является языком, целиком основанным на стековом программировании. Напомним, что при исследований пространства имен System.Collections (см. главу 7) мы с вами выяснили, что тип stack может использоваться для добавления значения в стек, а также для удаления из стека значения, размещенного на вершине стека. Конечно, разработчики CIL-приложений для загрузки и выгрузки значений не используют непосредственно объект System.Сollections.Stack, однако они применяют аналогичные операции.

Формально объект, используемый для хранения набора значений, называется виртуальным стеком выполнения. Вы сможете убедиться в том, что CIL предлагает целый ряд кодов операций, которые используются для добавления значения в стек: соответствующий процесс называется загрузкой. Точно так же CIL определяет целый ряд других кодов операций, которые переносят значение с вершины стека в память (например, в локальную переменную): для обозначения этого процесса используется термин сохранение.

В CIL просто невозможно получить доступ к элементам данных непосредственно, и это касается как локально определенных переменных, так и входных аргументов методов, а также полей данных типов. Нужно сначала явно загрузить элемент в стек, чтобы затем "вытолкнуть" его оттуда для дальнейшего использования (помните об этом, ведь именно поэтому блок программного кода CIL может казаться несколько избыточным).

Чтобы понять, как CIL использует стековую модель, рассмотрим простой C#-метод PrintMessage(), который не имеет аргументов и ничего не возвращает. В рамках реализации этого метода вы просто выводите значение локальной строковой переменной в поток стандартного вывода.

public void PrintMessage() {

 string myMessage = "Привет.";

 Consolе.WriteLine(myMessage);

}

Если рассмотреть результат трансляции этого метода компилятором C# в термины CIL, вы сразу заметите, что метод PrintMessage() определяет ячейку хранения для локальной переменной, используя директиву.locals. Локальная строка затем загружается и сохраняется в этой локальной неременной с помощью кодов операций ldstr (загрузка строки) и stloc.0 (это можно прочитать, как "запомнить текущее значение в локальной переменной с индексом 0").

Значение (снова с индексом 0) затем загружается в память с помощью кода операции ldloc.0 ("загрузить локальный аргумент с индексом 0") для использования в вызове метода System.Console.WriteLine() (указанном с помощью кода операции call). Наконец, происходит возврат из функции через код операции ret.

.method public hidebysig instance void PrintMessage() cil managed {

 .maxstack 1

 // Определение локальной строковой переменной (с индексом 0).

 .locals init ([0] string myMessage)

 // Загрузка строки со значением "Привет."

 ldstr "Привет."

 // Сохранение строкового значения в стеке в локальной переменной.

 stloc.0

 // Загрузка значения с индексом 0.

 ldloc.0

 // Вызов метода с текущим значением.

 call void [mscorlib]System.Console::WriteLine(string)

 ret

}

Замечание. В программном коде CIL поддерживаются комментарии, использующие синтаксис двойной косой черты (а также синтаксис /*…*/). Как и в C#, компилятором CIL комментарии просто игнорируются.