《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

  另外,提起上文中的ldloc.0这样的指令,笔者一开始也是未明其意。其实,在clr执行程序的过程中,涉及到的内存使用主要有三类,除了我们常说的托管堆,每个线程都有自己专属的两个栈:计算栈(Evaluation Stack)和调用栈(Call Stack)。这两个栈都经常被提到,又都专属于clr线程,所以有的时候会混,其实它们的功能是完全不同的。调用栈就是最传统的用来记录方法调用次序的栈,每个线程会得到1MB的调用栈空间,每调用一个新的方法都会压栈,传入参数、临时变量等都会压入栈中,待方法执行完毕就完全释放。ldloc.0中的"0",所指的就是当前调用栈上的第一个临时变量。而计算栈在某种程度上可以视为clr虚拟机的寄存器(这么说只是因为它是和虚机上的计算功能直接交互的存储单位),我们常说的clr虚机是完全基于“栈”的,指的就是计算栈。虚机上的所有操作,都要从计算栈的参数,结果也都会压入到计算栈顶。计算栈的单位既不是字,也不是字节,而是slot(怎么翻译合适呢,咳咳),根据slot内所装数据类型的不同,slot的大小也不同。当clr栈顶的slot里取到数据之后,在运算之前会检验数据的类型,如果无法转换成运算期望的类型,会抛出异常。蔡学庸在msdn上有篇文章,里面的一个贴图看着很明白,就在这里借用一下吧:

 参考资料:MSIL instruction table

posted @ 2007-07-10 19:53  EagleFish(邢瑜琨)  阅读(1190)  评论(7编辑  收藏  举报