Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

Имейте в виду, что различные коды операций CIL будут неявно извлекать значения из стека во время выполнения своих задач. Например, при вычитании одного числа из другого с применением кода операции

sub
должно быть очевидным то, что перед самим вычислением операция
sub
должна извлечь из стека два следующих доступных значения. Результат вычисления снова помещается в стек.

Директива .maxstack

При написании

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

.method public hidebysig instance void

Speak cil managed

{

// Внутри области действия этого метода в стеке находится

// в точности одно значение (строковый литерал).

.maxstack 1

ldstr "Hello there..."

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

ret

}

Объявление локальных переменных в CIL

Теперь давайте посмотрим, как объявлять локальные переменные. Предположим, что необходимо построить в CIL метод по имени

MyLocalVariables
, который не принимает аргументы и возвращает
void
, и определить в нем три локальные переменные с типами
System.String
,
System.Int32
и
System.Object
. В C# такой метод выглядел бы следующим образом (вспомните, что локальные переменные не получают стандартные значения и потому перед использованием должны быть инициализированы):

public static void MyLocalVariables

{

string myStr = "CIL code is fun!";

int myInt = 33;

object myObj = new object;

}

А вот как реализовать метод

MyLocalVariables
на языке CIL:

.method public hidebysig static void

MyLocalVariables cil managed

{

.maxstack 8

// Определить три локальные переменные.

.locals init (string myStr, int32 myInt, object myObj)

// Загрузить строку в виртуальный стек выполнения.

ldstr "CIL code is fun!"

// Извлечь текущее значение и сохранить его в локальной переменной [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
), т.к. индексация в виртуальном стеке выполнения начинается с нуля.

Поделиться с друзьями: