《Inside Microsoft IL Assembler》学习笔记2:让IL代码简短些
还是学习笔记1中的那个例子,可以改成如下的样子,功能不变,但运行时占用的资源会小一些。我在所有和笔记1中代码有了变化的位置加上了注释,如下:
1//----------- Program header
2.assembly extern mscorlib { }
3.assembly OddOrEven { }
4.module OddOrEven.exe
5//----------- Class declaration
6.namespace Odd.or {
7 .class public auto ansi Even extends [mscorlib]System.Object {
8//----------- Field declaration
9 .field public static int32 val
10//----------- Method declaration
11 .method public static void check( ) cil managed {
12 .entrypoint
13 .locals init (int32 Retval)
14 AskForNumber:
15 ldstr "Enter a number"
16 call void [mscorlib]System.Console::WriteLine(string)
17 call string [mscorlib]System.Console::ReadLine()
18 ldstr "%d" // 所有的字符串常量都不需要特殊的声明,il汇编器看到这样的字符串之后会自动将其加入到元数据中
19 ldsflda int32 Odd.or.Even::val
20 call vararg int32 sscanf(string,string,,int32*)
21 stloc.0 // java的虚机和.net clr都使用0,1,2,3这样的编号来表示本地变量。这里的"0"就指向唯一的一个本地变量Retval
22 ldloc.0 // 将第一个本地变量的值压栈
23 brfalse.s Error // brfalse.s指令是brfalse的简化指令。当指令被编译成操作码之后,brfalse接受的是一个4字节的参数,
24 //而brfalse.s仅接受一个单字节的参数,这意味着只能在代码段的{-128字节~~+128字节}的范围内(相对于当前位置而言)跳转。
25 ldsfld int32 Odd.or.Even::val
26 ldc.i4.1 // 向栈顶压入一个单字节常量,值为1(ldc.i4 1也是向栈顶压入1,但要用4个字节)
27 and
28 brfalse.s ItsEven
29 ldstr "odd!"
30 br.s PrintAndReturn
31 ItsEven:
32 ldstr "even!"
33 br.s PrintAndReturn
34 Error:
35 ldstr "How rude!"
36 PrintAndReturn:
37 call void [mscorlib]System.Console::WriteLine(string)
38 ldloc.0
39 brtrue.s AskForNumber
40 ret
41 } // End of method
42 } // End of class
43} // End of namespace
44//----------- Calling unmanaged code
45.method public static pinvokeimpl("msvcrt.dll" cdecl)
46 vararg int32 sscanf(string,string) cil managed { }
47
2.assembly extern mscorlib { }
3.assembly OddOrEven { }
4.module OddOrEven.exe
5//----------- Class declaration
6.namespace Odd.or {
7 .class public auto ansi Even extends [mscorlib]System.Object {
8//----------- Field declaration
9 .field public static int32 val
10//----------- Method declaration
11 .method public static void check( ) cil managed {
12 .entrypoint
13 .locals init (int32 Retval)
14 AskForNumber:
15 ldstr "Enter a number"
16 call void [mscorlib]System.Console::WriteLine(string)
17 call string [mscorlib]System.Console::ReadLine()
18 ldstr "%d" // 所有的字符串常量都不需要特殊的声明,il汇编器看到这样的字符串之后会自动将其加入到元数据中
19 ldsflda int32 Odd.or.Even::val
20 call vararg int32 sscanf(string,string,,int32*)
21 stloc.0 // java的虚机和.net clr都使用0,1,2,3这样的编号来表示本地变量。这里的"0"就指向唯一的一个本地变量Retval
22 ldloc.0 // 将第一个本地变量的值压栈
23 brfalse.s Error // brfalse.s指令是brfalse的简化指令。当指令被编译成操作码之后,brfalse接受的是一个4字节的参数,
24 //而brfalse.s仅接受一个单字节的参数,这意味着只能在代码段的{-128字节~~+128字节}的范围内(相对于当前位置而言)跳转。
25 ldsfld int32 Odd.or.Even::val
26 ldc.i4.1 // 向栈顶压入一个单字节常量,值为1(ldc.i4 1也是向栈顶压入1,但要用4个字节)
27 and
28 brfalse.s ItsEven
29 ldstr "odd!"
30 br.s PrintAndReturn
31 ItsEven:
32 ldstr "even!"
33 br.s PrintAndReturn
34 Error:
35 ldstr "How rude!"
36 PrintAndReturn:
37 call void [mscorlib]System.Console::WriteLine(string)
38 ldloc.0
39 brtrue.s AskForNumber
40 ret
41 } // End of method
42 } // End of class
43} // End of namespace
44//----------- Calling unmanaged code
45.method public static pinvokeimpl("msvcrt.dll" cdecl)
46 vararg int32 sscanf(string,string) cil managed { }
47
另外,提起上文中的ldloc.0这样的指令,笔者一开始也是未明其意。其实,在clr执行程序的过程中,涉及到的内存使用主要有三类,除了我们常说的托管堆,每个线程都有自己专属的两个栈:计算栈(Evaluation Stack)和调用栈(Call Stack)。这两个栈都经常被提到,又都专属于clr线程,所以有的时候会混,其实它们的功能是完全不同的。调用栈就是最传统的用来记录方法调用次序的栈,每个线程会得到1MB的调用栈空间,每调用一个新的方法都会压栈,传入参数、临时变量等都会压入栈中,待方法执行完毕就完全释放。ldloc.0中的"0",所指的就是当前调用栈上的第一个临时变量。而计算栈在某种程度上可以视为clr虚拟机的寄存器(这么说只是因为它是和虚机上的计算功能直接交互的存储单位),我们常说的clr虚机是完全基于“栈”的,指的就是计算栈。虚机上的所有操作,都要从计算栈的参数,结果也都会压入到计算栈顶。计算栈的单位既不是字,也不是字节,而是slot(怎么翻译合适呢,咳咳),根据slot内所装数据类型的不同,slot的大小也不同。当clr栈顶的slot里取到数据之后,在运算之前会检验数据的类型,如果无法转换成运算期望的类型,会抛出异常。蔡学庸在msdn上有篇文章,里面的一个贴图看着很明白,就在这里借用一下吧: