C#基础拾遗系列之一:先看懂IL代码
一、前言
首先,想说说为什么要写这样系列的文章,有时候在和同事朋友聊天的时候,经常会听到这样的话题:
(1)在这家公司没什么长进,代码太烂,学不到东西。(你有没有想想框架为什么这样写,代码还可以怎么去优化,比如公司使用Dapper,源码研究过没以及这样封装原因是啥)
(2)现在只会Ctrl + C Ctrl +V ,不排除有时为了效率,包括我自己有时候也懒的写直接复制粘贴 (是不是感觉距离语言的本质越来越远了)
(3)Ctrl + C Ctrl +V 时间长了,都有点怀疑自己是否有勇气面试其他公司 (是不是总给自己找借口,年级大了,不敢疯狂了,当然大家不要误解,我没鼓励大家跳槽)
(4)干了几年没什么提高 (无论要精通那门技术,我们都应该从其本质出发)
最近也在反思自己,之前看到博客园大神:fish-li 的一篇文章《Fish Li 该如何帮助您呢?》其中说到:如何做一个有追求的技术人员,受益匪浅。以及张善友老师分享的关于雷果果的技术之路,大家都羡慕这些大神,何曾想过他们背后的付出,不要再抱怨环境不好,环境好也是给这些有准备和有追求的人,很感谢有这样的前辈,现在的社会确实很浮躁,但与我何干,好了毒鸡汤就灌到这里,大家如果有共鸣的话,不要表达出来了,默默想想。
二、IL指令
(1)什么是IL
IL(Intermediate Language),它也称为CIL或者MSIL,中文就是“中间语言”。IL由ECMA组织(ECMA-335标准)提供完整的定义和规范。我们可以直接把C#源码编译为.exe或dll文件,但是此时编译出来的程序代码并不是CPU能直接执行的二进制代码,而是IL代码。
(2)反编译工具
在这里使用ILDasm.exe,不适用其他的反编译工具,这个工具在安装完毕VS2010后就已经存在了,大家可以在开始菜单中输入“IL”关键字进行搜索,如果没有的话,到C:\Program Files(x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX4.0Tools
(3)IL 指令表
名称 | 说明 |
Add | 将两个值相加并将结果推送到计算堆栈上。 |
Add.Ovf | 将两个整数相加,执行溢出检查,并且将结果推送到计算堆栈上。 |
Add.Ovf.Un | 将两个无符号整数值相加,执行溢出检查,并且将结果推送到计算堆栈上。 |
And | 计算两个值的按位“与”并将结果推送到计算堆栈上。 |
Arglist | 返回指向当前方法的参数列表的非托管指针。 |
Beq | 如果两个值相等,则将控制转移到目标指令。 |
Beq.S | 如果两个值相等,则将控制转移到目标指令(短格式)。 |
Bge | 如果第一个值大于或等于第二个值,则将控制转移到目标指令。 |
Bge.S | 如果第一个值大于或等于第二个值,则将控制转移到目标指令(短格式)。 |
Bge.Un | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令。 |
Bge.Un.S | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令(短格式)。 |
Bgt | 如果第一个值大于第二个值,则将控制转移到目标指令。 |
Bgt.S | 如果第一个值大于第二个值,则将控制转移到目标指令(短格式)。 |
Bgt.Un | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令。 |
Bgt.Un.S | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令(短格式)。 |
Ble | 如果第一个值小于或等于第二个值,则将控制转移到目标指令。 |
Ble.S | 如果第一个值小于或等于第二个值,则将控制转移到目标指令(短格式)。 |
Ble.Un | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值小于或等于第二个值,则将控制转移到目标指令。 |
Ble.Un.S | 当比较无符号整数值或不可排序的浮点值时,如果第一个值小于或等于第二个值,则将控制权转移到目标指令(短格式)。 |
Blt | 如果第一个值小于第二个值,则将控制转移到目标指令。 |
Blt.S | 如果第一个值小于第二个值,则将控制转移到目标指令(短格式)。 |
Blt.Un | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值小于第二个值,则将控制转移到目标指令。 |
Blt.Un.S | 当比较无符号整数值或不可排序的浮点型值时,如果第一个值小于第二个值,则将控制转移到目标指令(短格式)。 |
Bne.Un | 当两个无符号整数值或不可排序的浮点型值不相等时,将控制转移到目标指令。 |
Bne.Un.S | 当两个无符号整数值或不可排序的浮点型值不相等时,将控制转移到目标指令(短格式)。 |
Box | 将值类转换为对象引用(O 类型)。 |
Br | 无条件地将控制转移到目标指令。 |
Br.S | 无条件地将控制转移到目标指令(短格式)。 |
Break | 向公共语言结构 (CLI) 发出信号以通知调试器已撞上了一个断点。 |
Brfalse | 如果 value 为 false、空引用(Visual Basic 中的 Nothing)或零,则将控制转移到目标指令。 |
Brfalse.S | 如果 value 为 false、空引用或零,则将控制转移到目标指令。 |
Brtrue | 如果 value 为 true、非空或非零,则将控制转移到目标指令。 |
Brtrue.S | 如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 |
Call | 调用由传递的方法说明符指示的方法。 |
Calli | 通过调用约定描述的参数调用在计算堆栈上指示的方法(作为指向入口点的指针)。 |
Callvirt | 对对象调用后期绑定方法,并且将返回值推送到计算堆栈上。 |
Castclass | 尝试将引用传递的对象转换为指定的类。 |
Ceq | 比较两个值。如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上。 |
Cgt | 比较两个值。如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。 |
Cgt.Un | 比较两个无符号的或不可排序的值。如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。 |
Ckfinite | 如果值不是有限数,则引发 ArithmeticException。 |
Clt | 比较两个值。如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。 |
Clt.Un | 比较无符号的或不可排序的值 value1 和 value2。如果 value1 小于 value2,则将整数值 1 (int32 ) 推送到计算堆栈上;反之,将 0 ( int32 ) 推送到计算堆栈上。 |
Constrained | 约束要对其进行虚方法调用的类型。 |
Conv.I | 将位于计算堆栈顶部的值转换为 native int。 |
Conv.I1 | 将位于计算堆栈顶部的值转换为 int8,然后将其扩展(填充)为 int32。 |
Conv.I2 | 将位于计算堆栈顶部的值转换为 int16,然后将其扩展(填充)为 int32。 |
Conv.I4 | 将位于计算堆栈顶部的值转换为 int32。 |
Conv.I8 | 将位于计算堆栈顶部的值转换为 int64。 |
Conv.Ovf.I | 将位于计算堆栈顶部的有符号值转换为有符号 native int,并在溢出时引发 OverflowException。 |
Conv.Ovf.I.Un | 将位于计算堆栈顶部的无符号值转换为有符号 native int,并在溢出时引发 OverflowException。 |
Conv.Ovf.I1 | 将位于计算堆栈顶部的有符号值转换为有符号 int8 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.I1.Un | 将位于计算堆栈顶部的无符号值转换为有符号 int8 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.I2 | 将位于计算堆栈顶部的有符号值转换为有符号 int16 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.I2.Un | 将位于计算堆栈顶部的无符号值转换为有符号 int16 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.I4 | 将位于计算堆栈顶部的有符号值转换为有符号 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.I4.Un | 将位于计算堆栈顶部的无符号值转换为有符号 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.I8 | 将位于计算堆栈顶部的有符号值转换为有符号 int64,并在溢出时引发 OverflowException。 |
Conv.Ovf.I8.Un | 将位于计算堆栈顶部的无符号值转换为有符号 int64,并在溢出时引发 OverflowException。 |
Conv.Ovf.U | 将位于计算堆栈顶部的有符号值转换为 unsigned native int,并在溢出时引发 OverflowException。 |
Conv.Ovf.U.Un | 将位于计算堆栈顶部的无符号值转换为 unsigned native int,并在溢出时引发 OverflowException。 |
Conv.Ovf.U1 | 将位于计算堆栈顶部的有符号值转换为 unsigned int8 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.U1.Un | 将位于计算堆栈顶部的无符号值转换为 unsigned int8 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.U2 | 将位于计算堆栈顶部的有符号值转换为 unsigned int16 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.U2.Un | 将位于计算堆栈顶部的无符号值转换为 unsigned int16 并将其扩展为 int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.U4 | 将位于计算堆栈顶部的有符号值转换为 unsigned int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.U4.Un | 将位于计算堆栈顶部的无符号值转换为 unsigned int32,并在溢出时引发 OverflowException。 |
Conv.Ovf.U8 | 将位于计算堆栈顶部的有符号值转换为 unsigned int64,并在溢出时引发 OverflowException。 |
Conv.Ovf.U8.Un | 将位于计算堆栈顶部的无符号值转换为 unsigned int64,并在溢出时引发 OverflowException。 |
Conv.R.Un | 将位于计算堆栈顶部的无符号整数值转换为 float32。 |
Conv.R4 | 将位于计算堆栈顶部的值转换为 float32。 |
Conv.R8 | 将位于计算堆栈顶部的值转换为 float64。 |
Conv.U | 将位于计算堆栈顶部的值转换为 unsigned native int,然后将其扩展为 native int。 |
Conv.U1 | 将位于计算堆栈顶部的值转换为 unsigned int8,然后将其扩展为 int32。 |
Conv.U2 | 将位于计算堆栈顶部的值转换为 unsigned int16,然后将其扩展为 int32。 |
Conv.U4 | 将位于计算堆栈顶部的值转换为 unsigned int32,然后将其扩展为 int32。 |
Conv.U8 | 将位于计算堆栈顶部的值转换为 unsigned int64,然后将其扩展为 int64。 |
Cpblk | 将指定数目的字节从源地址复制到目标地址。 |
Cpobj | 将位于对象(&、* 或 native int 类型)地址的值类型复制到目标对象(&、* 或 native int 类型)的地址。 |
Div | 将两个值相除并将结果作为浮点(F 类型)或商(int32 类型)推送到计算堆栈上。 |
Div.Un | 两个无符号整数值相除并将结果 ( int32 ) 推送到计算堆栈上。 |
Dup | 复制计算堆栈上当前最顶端的值,然后将副本推送到计算堆栈上。 |
Endfilter | 将控制从异常的 filter 子句转移回公共语言结构 (CLI) 异常处理程序。 |
Endfinally | 将控制从异常块的 fault 或 finally 子句转移回公共语言结构 (CLI) 异常处理程序。 |
Initblk | 将位于特定地址的内存的指定块初始化为给定大小和初始值。 |
Initobj | 将位于指定地址的值类型的每个字段初始化为空引用或适当的基元类型的 0。 |
Isinst | 测试对象引用(O 类型)是否为特定类的实例。 |
Jmp | 退出当前方法并跳至指定方法。 |
Ldarg | 将参数(由指定索引值引用)加载到堆栈上。 |
Ldarg.0 | 将索引为 0 的参数加载到计算堆栈上。 |
Ldarg.1 | 将索引为 1 的参数加载到计算堆栈上。 |
Ldarg.2 | 将索引为 2 的参数加载到计算堆栈上。 |
Ldarg.3 | 将索引为 3 的参数加载到计算堆栈上。 |
Ldarg.S | 将参数(由指定的短格式索引引用)加载到计算堆栈上。 |
Ldarga | 将参数地址加载到计算堆栈上。 |
Ldarga.S | 以短格式将参数地址加载到计算堆栈上。 |
Ldc.I4 | 将所提供的 int32 类型的值作为 int32 推送到计算堆栈上。 |
Ldc.I4.0 | 将整数值 0 作为 int32 推送到计算堆栈上。 |
Ldc.I4.1 | 将整数值 1 作为 int32 推送到计算堆栈上。 |
Ldc.I4.2 | 将整数值 2 作为 int32 推送到计算堆栈上。 |
Ldc.I4.3 | 将整数值 3 作为 int32 推送到计算堆栈上。 |
Ldc.I4.4 | 将整数值 4 作为 int32 推送到计算堆栈上。 |
Ldc.I4.5 | 将整数值 5 作为 int32 推送到计算堆栈上。 |
Ldc.I4.6 | 将整数值 6 作为 int32 推送到计算堆栈上。 |
Ldc.I4.7 | 将整数值 7 作为 int32 推送到计算堆栈上。 |
Ldc.I4.8 | 将整数值 8 作为 int32 推送到计算堆栈上。 |
Ldc.I4.M1 | 将整数值 -1 作为 int32 推送到计算堆栈上。 |
Ldc.I4.S | 将提供的 int8 值作为 int32 推送到计算堆栈上(短格式)。 |
Ldc.I8 | 将所提供的 int64 类型的值作为 int64 推送到计算堆栈上。 |
Ldc.R4 | 将所提供的 float32 类型的值作为 F (float) 类型推送到计算堆栈上。 |
Ldc.R8 | 将所提供的 float64 类型的值作为 F (float) 类型推送到计算堆栈上。 |
Ldelem | 按照指令中指定的类型,将指定数组索引中的元素加载到计算堆栈的顶部。 |
Ldelem.I | 将位于指定数组索引处的 native int 类型的元素作为 native int 加载到计算堆栈的顶部。 |
Ldelem.I1 | 将位于指定数组索引处的 int8 类型的元素作为 int32 加载到计算堆栈的顶部。 |
Ldelem.I2 | 将位于指定数组索引处的 int16 类型的元素作为 int32 加载到计算堆栈的顶部。 |
Ldelem.I4 | 将位于指定数组索引处的 int32 类型的元素作为 int32 加载到计算堆栈的顶部。 |
Ldelem.I8 | 将位于指定数组索引处的 int64 类型的元素作为 int64 加载到计算堆栈的顶部。 |
Ldelem.R4 | 将位于指定数组索引处的 float32 类型的元素作为 F 类型(浮点型)加载到计算堆栈的顶部。 |
Ldelem.R8 | 将位于指定数组索引处的 float64 类型的元素作为 F 类型(浮点型)加载到计算堆栈的顶部。 |
Ldelem.Ref | 将位于指定数组索引处的包含对象引用的元素作为 O 类型(对象引用)加载到计算堆栈的顶部。 |
Ldelem.U1 | 将位于指定数组索引处的 unsigned int8 类型的元素作为 int32 加载到计算堆栈的顶部。 |
Ldelem.U2 | 将位于指定数组索引处的 unsigned int16 类型的元素作为 int32 加载到计算堆栈的顶部。 |
Ldelem.U4 | 将位于指定数组索引处的 unsigned int32 类型的元素作为 int32 加载到计算堆栈的顶部。 |
Ldelema | 将位于指定数组索引的数组元素的地址作为 & 类型(托管指针)加载到计算堆栈的顶部。 |
Ldfld | 查找对象中其引用当前位于计算堆栈的字段的值。 |
Ldflda | 查找对象中其引用当前位于计算堆栈的字段的地址。 |
Ldftn | 将指向实现特定方法的本机代码的非托管指针(native int 类型)推送到计算堆栈上。 |
Ldind.I | 将 native int 类型的值作为 native int 间接加载到计算堆栈上。 |
Ldind.I1 | 将 int8 类型的值作为 int32 间接加载到计算堆栈上。 |
Ldind.I2 | 将 int16 类型的值作为 int32 间接加载到计算堆栈上。 |
Ldind.I4 | 将 int32 类型的值作为 int32 间接加载到计算堆栈上。 |
Ldind.I8 | 将 int64 类型的值作为 int64 间接加载到计算堆栈上。 |
Ldind.R4 | 将 float32 类型的值作为 F (float) 类型间接加载到计算堆栈上。 |
Ldind.R8 | 将 float64 类型的值作为 F (float) 类型间接加载到计算堆栈上。 |
Ldind.Ref | 将对象引用作为 O(对象引用)类型间接加载到计算堆栈上。 |
Ldind.U1 | 将 unsigned int8 类型的值作为 int32 间接加载到计算堆栈上。 |
Ldind.U2 | 将 unsigned int16 类型的值作为 int32 间接加载到计算堆栈上。 |
Ldind.U4 | 将 unsigned int32 类型的值作为 int32 间接加载到计算堆栈上。 |
Ldlen | 将从零开始的、一维数组的元素的数目推送到计算堆栈上。 |
Ldloc | 将指定索引处的局部变量加载到计算堆栈上。 |
Ldloc.0 | 将索引 0 处的局部变量加载到计算堆栈上。 |
Ldloc.1 | 将索引 1 处的局部变量加载到计算堆栈上。 |
Ldloc.2 | 将索引 2 处的局部变量加载到计算堆栈上。 |
Ldloc.3 | 将索引 3 处的局部变量加载到计算堆栈上。 |
Ldloc.S | 将特定索引处的局部变量加载到计算堆栈上(短格式)。 |
Ldloca | 将位于特定索引处的局部变量的地址加载到计算堆栈上。 |
Ldloca.S | 将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)。 |
Ldnull | 将空引用(O 类型)推送到计算堆栈上。 |
Ldobj | 将地址指向的值类型对象复制到计算堆栈的顶部。 |
Ldsfld | 将静态字段的值推送到计算堆栈上。 |
Ldsflda | 将静态字段的地址推送到计算堆栈上。 |
Ldstr | 推送对元数据中存储的字符串的新对象引用。 |
Ldtoken | 将元数据标记转换为其运行时表示形式,并将其推送到计算堆栈上。 |
Ldvirtftn | 将指向实现与指定对象关联的特定虚方法的本机代码的非托管指针(native int 类型)推送到计算堆栈上。 |
Leave | 退出受保护的代码区域,无条件将控制转移到特定目标指令。 |
Leave.S | 退出受保护的代码区域,无条件将控制转移到目标指令(缩写形式)。 |
Localloc | 从本地动态内存池分配特定数目的字节并将第一个分配的字节的地址(瞬态指针,* 类型)推送到计算堆栈上。 |
Mkrefany | 将对特定类型实例的类型化引用推送到计算堆栈上。 |
Mul | 将两个值相乘并将结果推送到计算堆栈上。 |
Mul.Ovf | 将两个整数值相乘,执行溢出检查,并将结果推送到计算堆栈上。 |
Mul.Ovf.Un | 将两个无符号整数值相乘,执行溢出检查,并将结果推送到计算堆栈上。 |
Neg | 对一个值执行求反并将结果推送到计算堆栈上。 |
Newarr | 将对新的从零开始的一维数组(其元素属于特定类型)的对象引用推送到计算堆栈上。 |
Newobj | 创建一个值类型的新对象或新实例,并将对象引用(O 类型)推送到计算堆栈上。 |
Nop | 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。 |
Not | 计算堆栈顶部整数值的按位求补并将结果作为相同的类型推送到计算堆栈上。 |
Or | 计算位于堆栈顶部的两个整数值的按位求补并将结果推送到计算堆栈上。 |
Pop | 移除当前位于计算堆栈顶部的值。 |
Prefix1 | 基础结构。此指令为保留指令。 |
Prefix2 | 基础结构。此指令为保留指令。 |
Prefix3 | 基础结构。此指令为保留指令。 |
Prefix4 | 基础结构。此指令为保留指令。 |
Prefix5 | 基础结构。此指令为保留指令。 |
Prefix6 | 基础结构。此指令为保留指令。 |
Prefix7 | 基础结构。此指令为保留指令。 |
Prefixref | 基础结构。此指令为保留指令。 |
Readonly | 指定后面的数组地址操作在运行时不执行类型检查,并且返回可变性受限的托管指针。 |
Refanytype | 检索嵌入在类型化引用内的类型标记。 |
Refanyval | 检索嵌入在类型化引用内的地址(& 类型)。 |
Rem | 将两个值相除并将余数推送到计算堆栈上。 |
Rem.Un | 将两个无符号值相除并将余数推送到计算堆栈上。 |
Ret | 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。 |
Rethrow | 再次引发当前异常。 |
Shl | 将整数值左移(用零填充)指定的位数,并将结果推送到计算堆栈上。 |
Shr | 将整数值右移(保留符号)指定的位数,并将结果推送到计算堆栈上。 |
Shr.Un | 将无符号整数值右移(用零填充)指定的位数,并将结果推送到计算堆栈上。 |
Sizeof | 将提供的值类型的大小(以字节为单位)推送到计算堆栈上。 |
Starg | 将位于计算堆栈顶部的值存储到位于指定索引的参数槽中。 |
Starg.S | 将位于计算堆栈顶部的值存储在参数槽中的指定索引处(短格式)。 |
Stelem | 用计算堆栈中的值替换给定索引处的数组元素,其类型在指令中指定。 |
Stelem.I | 用计算堆栈上的 native int 值替换给定索引处的数组元素。 |
Stelem.I1 | 用计算堆栈上的 int8 值替换给定索引处的数组元素。 |
Stelem.I2 | 用计算堆栈上的 int16 值替换给定索引处的数组元素。 |
Stelem.I4 | 用计算堆栈上的 int32 值替换给定索引处的数组元素。 |
Stelem.I8 | 用计算堆栈上的 int64 值替换给定索引处的数组元素。 |
Stelem.R4 | 用计算堆栈上的 float32 值替换给定索引处的数组元素。 |
Stelem.R8 | 用计算堆栈上的 float64 值替换给定索引处的数组元素。 |
Stelem.Ref | 用计算堆栈上的对象 ref 值(O 类型)替换给定索引处的数组元素。 |
Stfld | 用新值替换在对象引用或指针的字段中存储的值。 |
Stind.I | 在所提供的地址存储 native int 类型的值。 |
Stind.I1 | 在所提供的地址存储 int8 类型的值。 |
Stind.I2 | 在所提供的地址存储 int16 类型的值。 |
Stind.I4 | 在所提供的地址存储 int32 类型的值。 |
Stind.I8 | 在所提供的地址存储 int64 类型的值。 |
Stind.R4 | 在所提供的地址存储 float32 类型的值。 |
Stind.R8 | 在所提供的地址存储 float64 类型的值。 |
Stind.Ref | 存储所提供地址处的对象引用值。 |
Stloc | 从计算堆栈的顶部弹出当前值并将其存储到指定索引处的局部变量列表中。 |
Stloc.0 | 从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。 |
Stloc.1 | 从计算堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中。 |
Stloc.2 | 从计算堆栈的顶部弹出当前值并将其存储到索引 2 处的局部变量列表中。 |
Stloc.3 | 从计算堆栈的顶部弹出当前值并将其存储到索引 3 处的局部变量列表中。 |
Stloc.S | 从计算堆栈的顶部弹出当前值并将其存储在局部变量列表中的 index 处(短格式)。 |
Stobj | 将指定类型的值从计算堆栈复制到所提供的内存地址中。 |
Stsfld | 用来自计算堆栈的值替换静态字段的值。 |
Sub | 从其他值中减去一个值并将结果推送到计算堆栈上。 |
Sub.Ovf | 从另一值中减去一个整数值,执行溢出检查,并且将结果推送到计算堆栈上。 |
Sub.Ovf.Un | 从另一值中减去一个无符号整数值,执行溢出检查,并且将结果推送到计算堆栈上。 |
Switch | 实现跳转表。 |
Tailcall | 执行后缀的方法调用指令,以便在执行实际调用指令前移除当前方法的堆栈帧。 |
Throw | 引发当前位于计算堆栈上的异常对象。 |
Unaligned | 指示当前位于计算堆栈上的地址可能没有与紧接的 ldind、stind、ldfld、stfld、ldobj、stobj、initblk 或 cpblk 指令的自然大小对齐。 |
Unbox | 将值类型的已装箱的表示形式转换为其未装箱的形式。 |
Unbox.Any | 将指令中指定类型的已装箱的表示形式转换成未装箱形式。 |
Volatile | 指定当前位于计算堆栈顶部的地址可以是易失的,并且读取该位置的结果不能被缓存,或者对该地址的多个存储区不能被取消。 |
Xor | 计算位于计算堆栈顶部的两个值的按位异或,并且将结果推送到计算堆栈上。 |
三、使用工具查看IL代码
案例一:
namespace ConsoleApplication1 { class Program { static void Main(string[] args) { string helloString = "Hello World"; Console.WriteLine(helloString); } } }
从上图可以看到IL结构:包含MANIFEST文件和ConsoleApplication1,其中MANIFEST是一个清单文件,主要包括程序集的一些属性,例如程序集名称、版本号、哈希算法、程序集模块,以及对外部引用程序的引用项目。.Program类是我们要主要介绍的内容。
(1)MANIFEST清单文件介绍
// Metadata version: v4.0.30319 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } .assembly ConsoleApplication1 { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. // --- 下列自定义属性会自动添加,不要取消注释 ------- // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 13 43 6F 6E 73 6F 6C 65 41 70 70 6C 69 63 // ...ConsoleApplic 61 74 69 6F 6E 31 00 00 ) // ation1.. .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 13 43 6F 6E 73 6F 6C 65 41 70 70 6C 69 63 // ...ConsoleApplic 61 74 69 6F 6E 31 00 00 ) // ation1.. .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 12 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright .. 20 32 30 31 38 00 00 ) // 2018.. .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 62 35 32 39 36 38 63 32 2D 64 66 63 33 // ..$b52968c2-dfc3 2D 34 65 38 31 2D 38 32 64 32 2D 64 39 66 35 62 // -4e81-82d2-d9f5b 62 33 32 38 33 64 37 00 00 ) // b3283d7.. .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0.. .custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1C 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework 2C 56 65 72 73 69 6F 6E 3D 76 34 2E 35 2E 32 01 // ,Version=v4.5.2. 00 54 0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 // .T..FrameworkDis 70 6C 61 79 4E 61 6D 65 14 2E 4E 45 54 20 46 72 // playName..NET Fr 61 6D 65 77 6F 72 6B 20 34 2E 35 2E 32 ) // amework 4.5.2 .hash algorithm 0x00008004 .ver 1:0:0:0 } .module ConsoleApplication1.exe // MVID: {A09A2101-9A49-483A-A224-5D2E14D231A6} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00020003 // ILONLY 32BITREQUIRED // Image base: 0x01110000
View Code
介绍:
指定当前程序集需要引用的外部程序集,如上面 ConsoleApplication1.exe就是引用了mscorlib.
.publickeytoken = (标记 ) 指定所引用程序集的实际公钥标记。公钥能唯一确定程序集。
.ver:指定引用程序集的版本
.assembly 程序集名称 指定程序集名称
.hash algorithm int32值 : 指定使用的hash算法
.ver :指定程序集版本号
.module ConsoleApplication1.exe 指定组成程序集的模块名称,在此示例中,程序集中只包含一个文件
.subsystem 0x0003 // WINDOWS_CUI 指定程序要求的应用程序环境。在此示例中,0x0003表示该可执行文件从控制台运行
.corflags 0x00020003 // ILONLY 32BITREQUIRED 当前是元数据中的一个保留字段
.class表示的Program是一个类,extends 代表Program类继承于程序集mscorlib中的System.Object类,这就告诉我们,在C#中所有的类的父类都是Object。
private为访问权限,表明该类是私有的。
auto:表明程序加载的时候内存布局是有CLR决定的,而不是由程序本身控制的。
ansi:表明类的编码为ansi编码
beforefieldinit :表明CLR可以在第一次访问静态字段之前的任何时刻执行类型构造函数。类型构造函数也就是构造函数,而使用beforefieldinit属性可以提高性能。
.ctor 表示构造函数
cil managed 表明方法体中的代码是IL代码,且是托管代码,即运行在CLR运行库中的代码
.maxstack 表明执行构造函数时,评估堆栈可容纳数据项的最大个数。评估堆栈是保存方法中所需变量的值的一个内存区域,该区域在方法执行结束时会被清空,或者存储一个返回值
IL_0000是代码行的开头。一般在IL_标记之前的部分为变量的声明和初始化操作。
ldarg.0表明加载第一个成员参数,其中ldarg是load argument 的缩写
call 指令一般用于调用静态方法,而这段代码中call指令并不是在调用静态函数,而是调用System.Object构造函数。另外一个指令则一般用来调用实例方法,它的调用过程是:首先检查被调用的函数是否为虚函数,
如果不是就直接调用,如果是则检查子类是否重写,如果有重写就调用子类中的实现,如果没有重写就继续调用原来函数。
ret 指令表示执行完毕,就是return的缩写
最后是Main函数,它是应用程序的入口函数:
hidebysig :指令表示如果当前类作为父类,用该指令标记的方法将不会被子类继承
.entrypoint :指令代表该函数是程序的入口函数,每个托管应用程序都有且只有一个入口函数,CLR加载的时候,首先从.entrypoint函数开始执行。
.locals init ([0] string helloString) 表示定义string 类型的变量,变量名成为:helloString
IL_0000: nop 表示不做任何操作 No Operation
ldstr “Hello World” : ldstr:推送对元数据中存储的字符串的新对象引用 表示:将字符串“Hello World” 压入评估栈,此时“Hello World” 处于评估栈的栈定,栈是一种数据结构,具有先进后出的特性。
stloc.0 :从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中(也就helloString) 在此示例中:就是把字符串”Hello World” 赋值给变量helloString
ldloc.0 :将索引 0 处的局部变量加载到计算堆栈上。也就是:把变量helloString 加载到计算堆栈上
(以ld为前缀的指令表示:入栈操作 st为前缀的指令则代表着出栈操作)
call :指令表示调用静态函数, 这里调用的是Console类中的WriteLine函数,把第0个局部变量输出到控制台中
案例二:
(1)IL基本类型
任何都有其内置的类型,IL语言也不例外,因为C#语言最终都会编译成IL代码,所以两者必然存在一种对应关系,
IL基本类型 | C#基本类型 |
Bool | System.Boolean |
Char | System.Char |
int8 | System.SByte |
int16 | System.Int16 |
int32 | System.Int32 |
int64 | System.Int64 |
float32 | System.Single |
float64 | System.Double |
object | System.Object |
string | System.String |
Array | System.Array |
(2)IL变量的声明
.locals 指令代表变量的声明,声明语句放在IL_标记前面。例如在前面的程序中,
.locals init ([0] string helloString) 就声明了一个名为helloString的变量,其中类型为string
(3)基本运算符
算数运算符:加法指令add 、乘法指令sub、出发指令div、以及求余指令rem等
位运算符:包括一元指令not 、与指令and、或指令or,结果以1 和 0 分别表示真、假,运算结果压入评估栈栈顶
比较运算:包括大于指令cgt、小于指令clt 和等于指令 ceq
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ILAdd { class Program { static void Main(string[] args) { int i = 2; int j = 3; int result = i + j; Console.WriteLine(result); } } }
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 17 (0x11) .maxstack 2 //声明三个变量 .locals init ([0] int32 i, [1] int32 j, [2] int32 result) //不做任何操作 IL_0000: nop //将整数值 2以4字节 作为 int32 推送到计算堆栈上 IL_0001: ldc.i4.2 //把评估栈顶的值弹出,并赋值给第0个局部变量(即 i),等价于i = 2 IL_0002: stloc.0 //将整数值 3以4字节 作为 int32 推送到计算堆栈上 IL_0003: ldc.i4.3 //把评估栈顶的值弹出,并赋值给第1个局部变量(即 j),等价于j = 2 IL_0004: stloc.1 //把第0个变量压入评估栈,即把变量 i 压入评估栈 IL_0005: ldloc.0 //把第1个变量压入评估栈,即把变量 j 压入评估栈 IL_0006: ldloc.1 //执行add操作,之后将把变量i和j清空,并把操作结果保存在评估栈站顶 IL_0007: add //把站顶的值弹出,并赋值给第二个局部变量(即result) ,此时result即为i+j 的值,因为栈顶为两个值的和 IL_0008: stloc.2 //将索引 2 处的局部变量加载到计算堆栈上。就是result IL_0009: ldloc.2 //call调用静态函数 IL_000a: call void [mscorlib]System.Console::WriteLine(int32) IL_000f: nop //返回 IL_0010: ret } // end of method Program::Main
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ILIf { class Program { static void Main(string[] args) { int i = 2; if (i>0) { Console.WriteLine("i为正数"); } else { Console.WriteLine("i为0或负数"); } } } }
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 40 (0x28) .maxstack 2 .locals init ([0] int32 i, [1] bool V_1) IL_0000: nop IL_0001: ldc.i4.2 IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: ldc.i4.0 //执行大于指令,比较i与0,运算结果存在于评估栈栈顶,1表示真,即i>0为真 IL_0005: cgt //就是把比较后的值赋值个变量V_1 IL_0007: stloc.1 // 把变量V_1压入评估栈 IL_0008: ldloc.1 //如果 value 为 false、空引用或零,则将控制转移到目标指令 IL_0009: brfalse.s IL_001a IL_000b: nop //推送对元数据中存储的字符串的新对象引用。 IL_000c: ldstr bytearray (69 00 3A 4E 63 6B 70 65 ) // i.:Nckpe IL_0011: call void [mscorlib]System.Console::WriteLine(string) IL_0016: nop IL_0017: nop //无条件地将控制转移到目标指令(短格式) IL_0018: br.s IL_0027 IL_001a: nop //推送对元数据中存储的字符串的新对象引用。 IL_001b: ldstr bytearray (69 00 3A 4E 30 00 16 62 1F 8D 70 65 ) // i.:N0..b..pe IL_0020: call void [mscorlib]System.Console::WriteLine(string) IL_0025: nop IL_0026: nop IL_0027: ret } // end of method Program::Main
案例三:const的本质
namespace ConstIL { class Program { static void Main(string[] args) {
Console.WriteLine(Person.num); } } public class Person { /// <summary> /// 这个就是一个所谓的const常量 /// </summary> public const int num = 10; } }
为什么可以直接类名.num?这种语法只有在该常量为static修饰是才可以,下面我们来看看IL:
.field public static literal int32 num = int32(0x0000000A)
看到没,const其实就是一个static的变量,一个静态的值,因为它是跟着类走的。而不是实例。所以 const的特征如下:
(1)固定不变的值。
(2)在编译的时候就已经确定了。
(3)在初始化的时候设置值
好了,先写到这里,回家前写这一篇,希望对你有帮助。
参考书籍:《Learning Hard》
参考文章:http://www.cnblogs.com/flyingbirds123/archive/2011/01/29/1947626.html
作者:郭峥
出处:http://www.cnblogs.com/runningsmallguo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。