学习Emit必不可少的, 会使用到IL中间代码. 初见IL代码, 让我有一种汇编的感觉, 让我想起了, 大学时, 学习8051的汇编语言. 多的就不扯了, 直接进入正题, OpCodes指令集是不是有一种让人望而却步的感觉, 那么多, 具体我没有数过, 但是肯定是比8051的指令多不少, 应该有200多个吧, 不过在实际使用的过程中, 肯定是用不到这么多的, 所以只要掌握一些常用的就够用了, 其余的, 查资料就可以了(大学老师当时也是这么教的, 实际使用中, 也确实是这样的)
一、示例
上一篇的结束部分, 贴出了一个中文注释版的OpCodes文件, 这部分内容跟那个文件是有很大关联的. 貌似, 贴在这一篇更合适呢...
嗯, 还是应该从示例里去开始讲
来源 : http://www.cnblogs.com/zery/p/3366175.html
static void Sum(int sum, string sumStr) { int a, b, c; a = 1; b = 2; c = 3; sum = a + b + c; sumStr = sum.ToString(); Console.WriteLine(sumStr); for (int i = 0; i < c; i++) { if (i > b) { Console.WriteLine("满足条件, 跳出循环"); break; } } Console.ReadKey(); }
.method private hidebysig static void Sum(int32 sum, string sumStr) cil managed { .maxstack 2 //定义函数代码所用堆栈的最大深度,也指Evaluation Stackk中最多能同时存在2个值 .locals init ( //变量的声明, (此时已经把num,num2,num3,num4,flag存入了Call Stack中的Record Frame中) [0] int32 num, [1] int32 num2, [2] int32 num3, [3] int32 num4, [4] bool flag) L_0000: nop //无任何操作, 可忽略 L_0001: ldc.i4.1 //加载 常量1 到栈中(压栈) L_0002: stloc.0 //从栈中把 常量1 拿出来, 赋值给num(出栈, 此时栈中已经没有东西了) L_0003: ldc.i4.2 //加载 常量2 到栈中(压栈) L_0004: stloc.1 L_0005: ldc.i4.3 L_0006: stloc.2 L_0007: ldloc.0 //将num变量压栈 L_0008: ldloc.1 //将变量num2压栈 (此时栈中有两个值, num2在上面, num在下面) L_0009: add //将num,num2求和的结果压栈(求和的时候, 会把两个值都提取出来, 所以结束后, 栈中只有一个结果值) L_000a: ldloc.2 //将num3压栈 L_000b: add //将num3,(num+num2)求和, 并压栈, 此时栈中, 只有最后的结果值 L_000c: starg.s sum //将栈顶的值传给传参sum(短格式) L_000e: ldarga.s sum //加载sum的地址到堆栈上(短格式) L_0010: call instance string [mscorlib]System.Int32::ToString() //调用ToString()方法, 完成格式转换,将结果值放入堆栈中 L_0015: starg.s sumStr //将堆栈顶的值传给传参sumStr(短格式) L_0017: ldarg.1 //将索引为1的传参(sumStr)加载到堆栈中 L_0018: call void [mscorlib]System.Console::WriteLine(string) //调用Console.WriteLine方法 L_001d: nop L_001e: ldc.i4.0 L_001f: stloc.3 // i = 0 L_0020: br.s L_0043 //无条件跳转到下面, 去判断 i<c 是否成立 L_0022: nop L_0023: ldloc.3 // i L_0024: ldloc.1 // b L_0025: cgt // i > b ? 1 : 0 L_0027: ldc.i4.0 //压栈0 L_0028: ceq //比较的结果在与0比较, (i > b ? 1 : 0) == 0 ? 1 : 0 L_002a: stloc.s flag //将结果存入本地变量flag L_002c: ldloc.s flag //加载flag到堆栈中 L_002e: brtrue.s L_003e //为真跳转到 L_003e L_0030: nop L_0031: ldstr "\u6ee1\u8db3\u6761\u4ef6, \u8df3\u51fa\u5faa\u73af" //"满足条件, 跳出循环" L_0036: call void [mscorlib]System.Console::WriteLine(string) L_003b: nop L_003c: br.s L_004d L_003e: nop L_003f: ldloc.3 // i L_0040: ldc.i4.1 // 1 L_0041: add // i + 1 L_0042: stloc.3 // i = i + 1 L_0043: ldloc.3 // i L_0044: ldloc.2 //c L_0045: clt // i < c ? 1 : 0 L_0047: stloc.s flag //将结果传给 flag L_0049: ldloc.s flag //加载flag变量到堆栈中 L_004b: brtrue.s L_0022 //为真跳转 L_0022 L_004d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() L_0052: pop //移除当前位于计算堆栈顶部的值 L_0053: ret //即为 return 标记 返回值 }
ldc.i4.1: i4--int32, 1--数值, 合起来就是 加载int32的数值1到堆栈中
stloc.0: 0--前面声明的locals变量组中的第0个 将堆栈顶的值付给locals0变量
ldloc.0: 加载locals0变量到堆栈中
add : 将栈顶的两个值求和, 并将结果压栈
二、常用的指令
*:https://en.wikipedia.org/wiki/List_of_CIL_instructions
来源:http://blog.csdn.net/joyhen/article/details/47276433
1. 常用的加载类指令
ldarg (及多个变化形式) | ld -- load , arg -- argument, 对这个大家都不陌生吧, 就不多解释了 加载方法的参数的值到栈中。除了泛型ldarg(需要一个索引作为参数),还有后其他很多的变化形式。'.'有个数字后缀的ldarg操作码来指定需要加载的参数。 a -- address, s -- short ldarga/ldarga.s表示的是加载参数的地址, 而不是加载参数的值 |
ldc (及多个变化形式) | c -- constant, const这个关键字大家肯定都很熟了, constant表示常量 加载一个常数到栈中 Ldc.I4.2 i4表示是int32的值(1个表示8位), 2表示常量 |
ldfld (及多个变化形式) | 加载一个对象实例的成员到栈中 |
ldloc (及多个变化形式) | loc -- locals 加载一个本地变量到栈中 |
ldobj | 获得一个堆对象的所有数据,并将它们放置到栈中. OpCodes:将地址指向的值类型对象复制到计算堆栈的顶部。 |
ldstr | 加载一个字符串数据到栈中 |
2. 常用的弹出操作指令
pop | 删除当前栈顶的值,但是并不影响存储的值 |
starg | st -- store 存储栈顶的值到给出方法的参数,根据索引确定这个参数. OpCodes:将位于计算堆栈顶部的值存储到位于指定索引的参数槽中 |
stloc (及多个变化形式) | 弹出当前栈顶的值并存储在一个本地变量列表中,根据所以确定这个参数 |
stobj | 从栈中复制一个特定的类型到指定的内存地址 |
stfld | 用从栈中获得的值替换对象成员的值 |
3. 常用的其他操作类指令
add, sub, mul, div, rem | 用于两个数加减乘除求模, 并将结果推送到计算堆栈上 |
and, or, not, xor | 用于在两个值上进行二进制操作 |
ceq, cgt, clt | 用不同的方法比较两个在栈上的值 c -- compare ceq:是否相等 -- 如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上 cgt:是否大于 -- 如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。 cgt.un -- 比较两个无符号的或不可排序的值, un -- unsigned 无符号 clt:是否小于 -- 如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。 |
box, unbox | 在引用类型和值类型之间转换 box: 装箱 unbox: 拆箱 |
ret | 退出方法和返回一个值 |
beq, bgt,bge,ble, blt, switch | 控制方法中的条件分支 b -- break, eq,e -- equal beq:如果两个值相等,则将控制转移到目标指令; bgt:如果第一个值 > 第二个值,则将控制转移到目标指令 bge:如果第一个值 >= 第二个值,则将控制转移到目标指令 ble:如果第一个值 <= 第二个值,则将控制转移到目标指令 blt:如果第一个值 < 第二个值,则将控制转移到目标指令 switch:实现跳转表 所有的分支控制操作码都需要给出一个CIL代码标签作为条件为真的跳转目的地 |
brtrue | 如果 value 为 true、非空或非零,则将控制转移到目标指令 |
br/br.s | br:(无条件)中止到代码标签 br.s:无条件地将控制转移到目标指令(短格式) |
call | 调用一个成员 |
newarr, newobj | 在内存中创建一个新的数组或新的对象类型 newarr:将对新的从零开始的一维数组(其元素属于特定类型)的对象引用推送到计算堆栈上 newobj:创建一个值类型的新对象或新实例,并将对象引用(O 类型)推送到计算堆栈上 |
未完待续......