在MSIL简介系列的第2部分,我将会探索局部变量的使用。如果没有变量,那么一个程序将会很无聊。为了解释如何使用变量,让我们写一个简单的求和程序。
在一个MSIL函数中,变量可以通过.locals指令声明.
.locals init (int32 first, int32 second, int32 result)
这个代码为当前函数声明了3个局部变量。本例中,它们恰好是 int32类型, 是System.Int32类型的别名。 init表示每个变量都需要被初始化成默认值。也可以忽略变量名,那样的话你需要通过从0开始的下标来引用它们。当然,使用变量名可以提高可读性。
在继续讲解之前,我希望确定你了解MSIL如何使用栈。当你想要给一个指令传值时,这些值需要被放到栈中。为了读取这些值,指令从栈中把它们拿出来。相似的,当调用一个函数时,你需要把引用依次推入栈中。然后函数会从栈中取出引用。为了把一个值推入栈中,用ldloc指令指示拥有那个值的变量。为了从栈中弹出一个值,使用stloc指令指示你要在哪个变量中存储这个值。记住,值是直接存储在栈中的,但是对象不是。对象的引用被存储在栈中,但对象在堆中被分配。
下一步是从用户那里获取数字。
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
就像是我在Part 1中提到的,ldstr指令把字符串推入栈中并调用Write函数,Write函数把变量从栈中弹出。下一个call指令调用返回string的ReadLine函数。返回的string被推入栈中,由于string已经在栈中,我们简单地调用 Int32::parse 函数把字符串弹出并推入等值的int32。注意,我为了讲解更清楚,忽略了错误处理。stloc指令会弹出栈中的值并把它存储在first局部变量中。从用户获取第二个数字的方法和这个相同,只是第二个数字的值需要被存储在 ‘second’ 局部变量中。
既然我们已经从标准输入中获取了两个值,是时候把它们相加。add指令可以达到我们的目的。
ldloc first
ldloc second
add
stloc result
add指令从栈中弹出两个值并计算它们的和。为了把局部变量的值推入栈中,我们使用了ldloc指令(load local),当add指令完成的时候,它会把结果推入栈中。接着程序使用stloc指令(set local)从栈中弹出值,并将其存储在名为 ‘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)
我们使用WriteLine重载函数。WriteLine函数的每个参数,都必须依次被推入栈中。因为数字是作为 int32值类型被存储的,我们需要为每个值装箱;否则和函数签名不匹配。
ldloc指令把每个变量推入栈中。然后为每个int32参数使用box指令。装箱会从栈中弹出值,构造一个包含值的副本的对象,然后把引用推入栈中。
下面是一个完整的程序。
.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
}
这里例子还有需要注意的一点是,我指示了main函数最多使用4个栈槽。这是为了和WriteLine函数的4个参数相匹配。