轻松读懂IL

先说说学IL有什么用,有人可能觉得这玩意平常写代码又用不上,学了有个卵用。到底有没有卵用呢?暂且不说什么学了可以看看一些语法糖的实现,或对.Net理解更深一点这些虚头巴脑的东西。最重要的理由就是当面试官看你简历上写着精通C#时,问你一句:
“懂不懂IL?”
怎么回答?
“不好意思,那东西没什么卵用,所有没学。”
还是
“还行,可以探讨一下。”
你觉得哪个回答好呢,答得好才更有底气要到更多的薪资,多个几千也说不定,而这学起来花不了太多的时间。
很多人见到IL一大堆指令,和汇编一样,就感觉头大不想学了。其实IL本身逻辑很清楚,主要是把指令的意思搞明白就好办了。记指令只要记住规律就好,我把它们分为三类。

第一类:直观型

这一类的特点是一看名字就知道是干嘛的,不需要多讲,如下:

名称说明
Add将两个值相加并将结果推送到计算堆栈上
Sub从其他值中减去一个值并将结果推送到计算堆栈上
Mul将两个值相乘并将结果推送到计算堆栈上
Div将两个值相除并将结果作为浮点(F类型)或商(int32类型)推送到计算堆栈上
Rem将两个值相除并将余数推送到计算堆栈上
Xor计算位于计算堆栈顶部的两个值的按位异或,并且将结果推送到计算堆栈上
And计算两个值的按位”与”并将结果推送到计算堆栈上
Or计算位于堆栈顶部的两个整数值的按位求补并将结果推送到计算堆栈上
Not计算堆栈顶部整数值的按位求补并将结果作为相同的类型推送到计算堆栈上
Dup复制计算堆栈上当前最顶端的值,然后将副本推送到计算堆栈上
Neg对一个值执行求反并将结果推送到计算堆栈上
Ret从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上
Jmp退出当前方法并跳至指定方法
NewobjNew Object创建一个值类型的新对象或新实例,并将对象引用推送到计算堆栈上
NewarrNew Array将对新的从零开始的一维数组(其元素属于特定类型)的对象引用推送到计算堆栈上
Nop如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。Debug下的
Pop移除当前位于计算堆栈顶部的值
InitobjInit Object将位于指定地址的值类型的每个字段初始化为空引用或适当的基元类型的 0
IsinstIs Instance测试对象引用是否为特定类的实例
Sizeof将提供的值类型的大小(以字节为单位)推送到计算堆栈上
Box将值类转换为对象引用
Unbox将值类型的已装箱的表示形式转换为其未装箱的形式
Castclass尝试将引用传递的对象转换为指定的类
Switch实现跳转表
Throw引发当前位于计算堆栈上的异常对象
Call调用由传递的方法说明符指示的方法
Calli通过调用约定描述的参数调用在计算堆栈上指示的方法(作为指向入口点的指针)
Callvirt对对象调用后期绑定方法,并且将返回值推送到计算堆栈上

强调一下,三种Call用的场景不太一样:

  • Call:常用于调用编译时就确定的方法,可以直接去元数据里找方法,如静态函数、实例方法,也可以Call虚方法,不过只是call这个类型本身的虚方法,和实例的方法性质一样。另外,Call不做null检测。
  • Calli:MSDN上讲是直接调用指针指向的函数,具体场景没见过,有知道的朋友望不吝赐教。
  • Callvirt:可以调用实例方法和虚方法,调用虚方法时以多态方式调用,不能调用静态方法。Callvirt调用时会做null检测,如果实例是null,会抛出NullReferenceException异常,所以速度比Call慢点。

第二类:加载(Ld)和存储(St)

我们知道,C#程序运行时会有线程栈把参数、局部变量放上来,另外有个计算栈用来做函数里的计算,所以需要指令把值加载到计算栈上,算完后再把计算栈上的值存到线程栈上去,Ld和St这类指令专门干这些活。
比方说Ldloc_0:
这个可以拆开来看,Ld可以理解为Load,也就是加载;loc可以理解为local variable,也就是局部变量;后面的_0表示索引。连起来的意思就是:把索引为0的局部变量加载到计算栈上。对应的Ldloc_1就是把索引为1的局部变量加载到计算栈上,以此类推。
知道了Ld的意思,下面这些指令也就很容易理解了:
Ldstr=load string,
Ldnull=load null,
Ldobj=load object,
Ldfld=load field,
Ldflda=load field address,
Ldsfld=load static field,
Ldsflda=load static field address,
Ldelem=load element in array,
Ldarg=load argument,
Ldc表示加载数值,如Ldc_I4_0表示加载Int32类型的数字0,Ldc_I4_1表示加载Int32类型的数字1,以此类推。

关于后缀
_i[n]:[n]表示字节数,1个字节是8位,所以8*n的int,比如i1、i2、i4、i8,i1就是int8(byte)、i2就是int16(short)、i4就是int32(int)、i8就是int64(long)。
相似的还有_u1、_u2、_u4、_u8,分别表示unsigned int8(byte)、unsigned int16(short)、unsigned int32(int)、unsigned int64(long)。
_R4、_R8表示float和double。
_ovf(overflow)则表示进行溢出检查,溢出时会抛出异常。
_un(unsigned)表示无符号数。
_ref(reference)表示引用。
_S(short)表示短格式,比如Ldc_I4_S表示将提供的int8值作为int32推送到计算堆栈上。
_[n]比如_1、_2等,如果跟在i[n]后面则表示数值,其他都表示索引。如Ldc_I4_1表示加载数值1到计算堆栈上,再如Ldarg_0就是加载第一个参数到计算堆栈上。

注意:
Ldarg要特别注意一个问题,如果是实例方法的话Ldarg_0加载的是本身即this,Ldarg_1加载的才是函数的第一个参数;如果是静态方法,Ldarg_0就是加载第一个参数。

与Ld对应的就是St,可以理解为Store,意思是把计算堆栈上的值存到变量中去,Ld相关的指令很多都有St对应的,比如Stloc、Stfld、Stsfld、Stobj、Starg、Stelem等,就不多说了。

第三类:比较指令,比较大小或判断bool值

有一部分指令是比较之后跳转的,代码里的if就会产生这些指令,符合条件则跳转执行另一些代码。
1、以b开头:beq、bge、bgt、ble、blt、bne
先把b去掉看看:
eq:equivalent with,==
ge:greater than or equivalent with,>=
gt:greater than,>
le:less than or equivalent with,<=
lt:less than,<
ne:not equivalent with,!=
这样就很好理解了,beq IL_0005就是计算栈上两个值相等的话就跳转到IL_005,ble IL_0023就是第一个值小于或等于第二个值就跳转到IL_0023。

2、以br(break)开头:br、brfalse、brtrue
br是无条件跳转;
brfalse表示计算栈上的值为false/null/0时发生跳转;
brtrue表示计算栈上的值为tue/非空/非0时发生跳转。

3、以c开头:ceq、cgt、clt
该指令的用来计算bool值的,跟前面b开头的有点像。
ceq比较两个值,相等则将1(true)推到栈上,否则就把0(false)推到栈上;
cgt比较两个值,第一个大于第二个则将1(true)推到栈上,否则就把0(false)推到栈上;
clt比较两个值,第一个小于第二个则将1(true)推到栈上,否则就把0(false)推到栈上。

总结

以上三类是IL常用指令,把这些搞明白了,IL指令也就理解的七七八八了,如果遇到不熟悉的指令查一下也没问题。
IL其实不难,有没有用则仁者见仁,智者见智,学起来花不了太多时间,也没必要学太深,不过要有耐心,复杂的IL看起来还真是挺头痛。好在有工具ILSpy,可以在option里选择部分不编译看起来会简单些。
最后介绍两个工具:
.Net Reflector可以把用户自己编写的IL指令转化为正常代码,大家可以自己下载安装;
IL查看工具可以把正常代码转化为IL指令,vs2010中路径为C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\ildasm.exe,不同版本目录可能不太一样。
有了这两个工具当我们想用IL指令实现某一功能但不会写时,可以先用正常代码把功能写出来,在IL查看工具中查看IL代码是什么样的,然后自己再根据转化的IL代码逻辑使用IL指令实现想要的功能。

.Net Reflector Version 9.0.1.374 带注册机免费下载地址: http://download.csdn.net/detail/xiaouncle/9649783
最后我要感谢布鲁克石的文章:http://www.cnblogs.com/brookshi/p/5225801.html?ref=myread

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

changuncle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值