Введение в MSIL - Часть 2: Использование локальных переменных

Дата публикации 22 июл 2006

Введение в MSIL - Часть 2: Использование локальных переменных — Архив WASM.RU

В этой части я расскажу об использовании локальных переменных. Без них программы были бы не слишком интересны. Чтобы объяснить, как использовать переменные, давайте напишем простую программу для сложения чисел.

Внутри метода локальные переменные задаются с помощью директивы .locals.

Код (Text):
  1.  
  2. .locals init (int32 first,
  3.               int32 second,
  4.               int32 result)

Это утверждение декларирует, что у данного метода есть три локальные переменные. В этом случае они все оказались одного типа - int32, что является синонимом типа System.Int32. init задаёт, что переменные должны быть инициализированы значением по умолчанию данного типа. Также возможно опускать имена переменных, и в этом случае вы сможете обращаться к ним по их индексу (начиная с нуля). Разумеется, использование имён существенно повышает читабельность.

Прежде чем мы продолжим, я хочу убедиться, что вы понимаете, как используется стек в MSIL. Когда вы хотите передать требуемые значения инструкции, их нужно поместить в стек. Чтобы прочитать эти значения, инструкция берёт их из стека. Похожим образом, при вызове метода вам нужно поместить в стек ссылку на объект (если нужно), а за ней - аргументы. В процессе вызова метода все аргументы вместе с ссылкой на объект будут взяты из стека. Для помещения значения в стек используется инструкция ldloc, используемую с переменной, содержащей значение. Для того, чтобы взять значение из стека, используется инструкция stloc, которой указывается переменная, куда сохраняется значение. Также держите в уме, что значения (с учётом их типа) сохраняются прямо в стек, а объекты - нет, так как CLI этого не позволяет. Вместо этого в стек помещаются ссылки на них. Это похоже на C++-объект, находящийся в "куче", с указателем на него в стеке. Никогда не забывайте о стеке, читая эту серию статей. Это поможет вам понять, почему значения постоянно помещаются и берутся из него.

Следующим шагом будет получение чисел от пользователя.

Код (Text):
  1.  
  2. ldstr "First number: "
  3. call void [mscorlib]System.Console::Write(string)
  4. call string [mscorlib]System.Console::ReadLine()
  5. call int32 [mscorlib]System.Int32::Parse(string)
  6. stloc first

Как я упомянул в первой части, инструкция ldstr помещает строку в стек и, а инструкция call вызывает метод Write, беря свои аргументы из стека. Затем вызывается метод ReadLine, возвращающий строку, которая помещается в стек, и, поскольку она уже там, нам остаётся просто вызвать метод Int32::Parse, берущий строку из стека и помещающий в него соответствующий in32-эквиваент. Обратите внимание, что для простоты я опустил всё, что связано с обработкой ошибок. Затем инструкция stloc достаёт значение из стека и сохраняет его в локальную переменную 'first'. Получение следующего числа происходит примерно также, не считая, что значение сохраняется в переменной 'second'.

Теперь, прочитав два числа из стандартного потока ввода, настало время, чтобы их сложить. Для этого можно использовать инструкцию add.

Код (Text):
  1.  
  2. ldloc first
  3. ldloc second
  4. add
  5. stloc result

Она берёт два числа из стека и считает сумму. Чтобы поместить оба числа в стек, мы используем ldloc. После выполнения инструкции add полученный результат помещается в стек и программа сохраняет его в переменную 'result', используя stloc.

Последний шаг - это отобразить результат пользователю.

Код (Text):
  1.  
  2. ldstr "{0} + {1} = {2}"
  3.  
  4. ldloc first
  5. box int32
  6.  
  7. ldloc second
  8. box int32
  9.  
  10. ldloc result
  11. box int32
  12.  
  13. call void [mscorlib]System.Console::WriteLine(string, object, object, object)

Мы использовали перегруженный вариант WriteLine, принимающую форматирующую строку и три объекта. Каждый аргумент должен быть помещён в стек один за другим. Так как числа имеют тип int32, нам нужно сделать из них объекты, иначе получится несовпадение с сигнатурой метода.

Инструкция ldloc помещает каждый аргумент в стек. Затем для каждого аргумента int32 используется инструкция box. Она достаёт значение из стека, делает на его основе объект и помещает обратно в стек ссылку на него.

Вот полная программа.

Код (Text):
  1.  
  2. .method static void main()
  3. {
  4.     .entrypoint
  5.     .maxstack 4
  6.    
  7.     .locals init (int32 first,
  8.                   int32 second,
  9.                   int32 result)
  10.  
  11.     ldstr "First number: "
  12.     call void [mscorlib]System.Console::Write(string)
  13.     call string [mscorlib]System.Console::ReadLine()
  14.     call int32 [mscorlib]System.Int32::Parse(string)
  15.     stloc first
  16.    
  17.     ldstr "Second number: "
  18.     call void [mscorlib]System.Console::Write(string)
  19.     call string [mscorlib]System.Console::ReadLine()
  20.     call int32 [mscorlib]System.Int32::Parse(string)
  21.     stloc second
  22.  
  23.     ldloc first
  24.     ldloc second
  25.     add
  26.     stloc result
  27.  
  28.     ldstr "{0} + {1} = {2}"
  29.    
  30.     ldloc first
  31.     box int32
  32.    
  33.     ldloc second
  34.     box int32
  35.    
  36.     ldloc result
  37.     box int32
  38.    
  39.     call void [mscorlib]System.Console::WriteLine(string, object, object, object)    
  40.  
  41.     ret
  42. }

Последнее в этом примере, на что вам стоит обратить своё внимание, это то, что в методе указано максимальное количество используемых стек-слотов равное 4, что было сделано с учётом метода WriteLine, вызываемого в конце программы, которому требуется передать именно это количество аргументов. © Кенни Керр, пер. Aquila


0 1.752
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532