Введение в MSIL - Часть 2: Использование локальных переменных — Архив WASM.RU
В этой части я расскажу об использовании локальных переменных. Без них программы были бы не слишком интересны. Чтобы объяснить, как использовать переменные, давайте напишем простую программу для сложения чисел.
Внутри метода локальные переменные задаются с помощью директивы .locals.
Код (Text):
.locals init (int32 first, int32 second, int32 result)Это утверждение декларирует, что у данного метода есть три локальные переменные. В этом случае они все оказались одного типа - int32, что является синонимом типа System.Int32. init задаёт, что переменные должны быть инициализированы значением по умолчанию данного типа. Также возможно опускать имена переменных, и в этом случае вы сможете обращаться к ним по их индексу (начиная с нуля). Разумеется, использование имён существенно повышает читабельность.
Прежде чем мы продолжим, я хочу убедиться, что вы понимаете, как используется стек в MSIL. Когда вы хотите передать требуемые значения инструкции, их нужно поместить в стек. Чтобы прочитать эти значения, инструкция берёт их из стека. Похожим образом, при вызове метода вам нужно поместить в стек ссылку на объект (если нужно), а за ней - аргументы. В процессе вызова метода все аргументы вместе с ссылкой на объект будут взяты из стека. Для помещения значения в стек используется инструкция ldloc, используемую с переменной, содержащей значение. Для того, чтобы взять значение из стека, используется инструкция stloc, которой указывается переменная, куда сохраняется значение. Также держите в уме, что значения (с учётом их типа) сохраняются прямо в стек, а объекты - нет, так как CLI этого не позволяет. Вместо этого в стек помещаются ссылки на них. Это похоже на C++-объект, находящийся в "куче", с указателем на него в стеке. Никогда не забывайте о стеке, читая эту серию статей. Это поможет вам понять, почему значения постоянно помещаются и берутся из него.
Следующим шагом будет получение чисел от пользователя.
Код (Text):
ldstr "First number: " call void [mscorlib]System.Console::Write(string) call string [mscorlib]System.Console::ReadLine() call int32 [mscorlib]System.Int32::Parse(string) stloc firstКак я упомянул в первой части, инструкция ldstr помещает строку в стек и, а инструкция call вызывает метод Write, беря свои аргументы из стека. Затем вызывается метод ReadLine, возвращающий строку, которая помещается в стек, и, поскольку она уже там, нам остаётся просто вызвать метод Int32::Parse, берущий строку из стека и помещающий в него соответствующий in32-эквиваент. Обратите внимание, что для простоты я опустил всё, что связано с обработкой ошибок. Затем инструкция stloc достаёт значение из стека и сохраняет его в локальную переменную 'first'. Получение следующего числа происходит примерно также, не считая, что значение сохраняется в переменной 'second'.
Теперь, прочитав два числа из стандартного потока ввода, настало время, чтобы их сложить. Для этого можно использовать инструкцию add.
Код (Text):
ldloc first ldloc second add stloc resultОна берёт два числа из стека и считает сумму. Чтобы поместить оба числа в стек, мы используем ldloc. После выполнения инструкции add полученный результат помещается в стек и программа сохраняет его в переменную 'result', используя stloc.
Последний шаг - это отобразить результат пользователю.
Код (Text):
ldstr "{0} + {1} = {2}" ldloc first box int32 ldloc second box int32 ldloc result box int32 call void [mscorlib]System.Console::WriteLine(string, object, object, object)Мы использовали перегруженный вариант WriteLine, принимающую форматирующую строку и три объекта. Каждый аргумент должен быть помещён в стек один за другим. Так как числа имеют тип int32, нам нужно сделать из них объекты, иначе получится несовпадение с сигнатурой метода.
Инструкция ldloc помещает каждый аргумент в стек. Затем для каждого аргумента int32 используется инструкция box. Она достаёт значение из стека, делает на его основе объект и помещает обратно в стек ссылку на него.
Вот полная программа.
Код (Text):
.method static void main() { .entrypoint .maxstack 4 .locals init (int32 first, int32 second, int32 result) ldstr "First number: " call void [mscorlib]System.Console::Write(string) call string [mscorlib]System.Console::ReadLine() call int32 [mscorlib]System.Int32::Parse(string) stloc first ldstr "Second number: " call void [mscorlib]System.Console::Write(string) call string [mscorlib]System.Console::ReadLine() call int32 [mscorlib]System.Int32::Parse(string) stloc second ldloc first ldloc second add stloc result ldstr "{0} + {1} = {2}" ldloc first box int32 ldloc second box int32 ldloc result box int32 call void [mscorlib]System.Console::WriteLine(string, object, object, object) ret }Последнее в этом примере, на что вам стоит обратить своё внимание, это то, что в методе указано максимальное количество используемых стек-слотов равное 4, что было сделано с учётом метода WriteLine, вызываемого в конце программы, которому требуется передать именно это количество аргументов. © Кенни Керр, пер. Aquila
Введение в MSIL - Часть 2: Использование локальных переменных
Дата публикации 22 июл 2006