IL轻松学习
正则表达式: 第一类 :直观型这一类的特点是一看名字就知道是干嘛的,不需要多讲,如下:
第二类:加载(ld)和存储(st)我们知道,C#程序运行时会有线程栈把参数,局部变量放上来,另外还有个计算栈用来做函数里的计算。所以把值加载到计算栈上,算完后再把计算栈上的值存到线程栈上去,这类指令专门干这些活。 比方说 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, 关于后缀 .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)表示短格式,比如说正常的是用int32,加了.s的话就是用int8; .[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,starg,stelem等,就不多说了。 第三类:比较指令,比较大小或判断bool值有一部分是比较之后跳转的,代码里的 if 就会产生这些指令,符合条件则跳转执行另一些代码: 以b开头:beq,bge,bgt,ble,blt,bne 先把b去掉看看: gt:greaterthan,> 这样是不是很好理解了,beq IL_0005就是计算栈上两个值相等的话就跳转到IL_0005, ble IL_0023是第一个值小于或等于第二个值就跳转到IL_0023。 以br(break)开头:br,brfalse,brtrue, br是无条件跳转; brfalse表示计算栈上的值为 false/null/0 时发生跳转; brtrue表示计算栈上的值为 true/非空/非0 时发生跳转 还有一部分是c开头,算bool值的,和前面b开头的有点像: ceq 比较两个值,相等则将 1 (true) 推到栈上,否则就把 0 (false)推到栈上 cgt比较两个值,第一个大于第二个则将 1 (true) 推到栈上,否则就把 0 (false)推到栈上 clt 比较两个值,第一个小于第二个则将 1 (true) 推到栈上,否则就把 0 (false)推到栈上 以上就是三类常用的,把这些搞明白了,IL指令也就理解得七七八八了。就像看文章一样,认识大部分字后基本就不影响阅读了,不认识的猜下再查下,下次再看到也就认得了。 例子下面看个例子,随手写段简单的代码,是否合乎逻辑暂不考虑,主要是看IL: 源代码: 1 using System; 2 3 namespace ILLearn 4 { 5 class Program 6 { 7 const int WEIGHT = 60; 8 9 static void Main(string[] args) 10 { 11 var height = 170; 12 13 People people = new Developer("brook"); 14 15 var vocation = people.GetVocation(); 16 17 var healthStatus = People.IsHealthyWeight(height,WEIGHT) ? healthy" : not healthy"; 18 19 Console.WriteLine(${vocation} is {healthStatus}20 21 Console.ReadLine(); 22 } 23 } 24 25 abstract class People 26 { 27 public string Name { get; set; } 28 29 string GetVocation(); 30 31 bool IsHealthyWeight(int height,int weight) 32 { 33 var healthyWeight = (height - 80) * 0.7; 34 return weight <= healthyWeight * 1.1 && weight >= healthyWeight * 0.9; //标准体重是 (身高-80) * 0.7,区间在10%内都是正常范围 35 } 36 } 37 38 class Developer : People 39 { 40 public Developer(string name) 41 { 42 Name = name; 43 } 44 45 override string GetVocation() 46 { 47 return Developer48 } 49 } 50 } 在命令行里输入:csc /debug- /optimize+ /out:program.exe Program.cs 打开IL查看工具:C:Program Files (x86)Microsoft SDKsWindowsv10.0AbinNETFX 4.6 Toolsildasm.exe,不同版本可能目录不太一样。打开刚编译的program.exe文件,如下: 双击节点就可以查看IL,如: Developer的构造函数: 1 .method public hidebysig specialname rtspecialname
2 instance void .ctor(string name) cil managed
3 {
4 代码大小 14 (0xe)
5 .maxstack 8
6 IL_0000: ldarg.0 加载第1个参数,因为是实例,而实例的第1个参数始终是this
7 IL_0001: call instance void ILLearn.People::.ctor() 调用基类People的构造函数,而People也会调用Object的构造函数
8 IL_0006: ldarg.加载this
9 IL_0007: ldarg.1 加载第二个参数也就是name
10 IL_0008: call instance void ILLearn.People::set_Name(string) 调用this的 set_Name, set_Name这个函数是编译时为属性生成的
11 IL_000d: ret return
12 } end of method Developer::.ctor
Developer的GetVocation: 1 .method public hidebysig virtual instance string 虚函数
2 GetVocation() cil managed
3 {
4 代码大小 6 (0x6)
5 .maxstack 8 最大计算栈,默认是8
6 IL_0000: ldstr " 加载string "Developer"
7 IL_0005: ret 8 } end of method Developer::GetVocation
People的IsHealthyWeight: bool IsHealthyWeight(int32 height,0)">静态函数
2 int32 weight) cil managed
代码大小 52 (0x34)
3 最大计算栈大小
6 .locals init ([0] float64 healthyWeight) 局部变量
7 IL_0000: ldarg.加载第1个参数,因为是静态函数,所以第1个参数就是height
8 IL_0001: ldc.i4.s 80 ldc 加载数值, 加载80
9 IL_0003: sub 做减法,也就是 height-80,把结果放到计算栈上,前面两个已经移除了
10 IL_0004: conv.r8 转换成double,因为下面计算用到了double,所以要先转换
11 IL_0005: ldc.r8 0.69999999999999996 加载double数值 0.7, 为什么是0.69999999999999996呢,二进制存不了0.7,只能找个最相近的数
12 IL_000e: mul 计算栈上的两个相乘,也就是(height - 80) * 0.7
13 IL_000f: stloc.存到索引为0的局部变量(healthyWeight)
14 IL_0010: ldarg.加载第1个参数 weight
15 IL_0011: conv.r8 转换成double
16 IL_0012: ldloc.加载索引为0的局部变量(healthyWeight)
17 IL_0013: ldc.r8 1.1000000000000001 加载double数值 1.1, 看IL_0010到IL_0013,加载了3次,这个函数最多也是加载3次,所以maxstack为3
18 IL_001c: mul 计算栈上的两个相乘,也就是 healthyWeight * 1.1,这时计算栈上还有两个,第一个是weight,第二个就是这个计算结果
19 IL_001d: bgt.un.s IL_0032 比较这两个值,第一个大于第二个就跳转到 IL_0032,因为第一个大于第二个表示第一个条件weight <= healthyWeight * 1.1就是false,也操作符是&&,后面没必要再算,直接return 0
20 IL_001f: ldarg.21 IL_0020: conv.r8 22 IL_0021: ldloc.23 IL_0022: ldc.r8 0.90000000000000002 加载double数值 0.9
24 IL_002b: mul 计算栈上的两个相乘,也就是 healthyWeight * 0.9,128)">25 IL_002c: clt.un 比较大小,第一个小于第二个则把1放上去,否则放0上去
26 IL_002e: ldc.i4.0 加载数值0
27 IL_002f: ceq 比较大小,相等则把1放上去,否则放0上去
28 IL_0031: ret return 栈顶的数,为什么没用blt.un.s,因为IL_0033返回的是false
29 IL_0032: ldc.i4.30 IL_0033: ret return 栈顶的数
31 } end of method People::IsHealthyWeight
主函数Main: private hidebysig void Main(string[] args) cil managed
2 {
3 .entrypoint 这是入口
代码大小 67 (0x43)
大小为3的计算栈
6 .locals init (string V_0, 7 string V_1) 两个string类型的局部变量,本来还有个people的局部变量,被release方式优化掉了,因为只是调用了people的GetVocation,后面没用,所以可以不存
8 IL_0000: ldc.i4 0xaa 加载int型170
9 IL_0005: ldstr 加载string "brook"
10 IL_000a: newobj instance void ILLearn.Developer::.ctor(new一个Developer并把栈上的brook给构造函数
11 IL_000f: callvirt instance string ILLearn.People::GetVocation() 调用GetVocation
12 IL_0014: stloc.把上面计算的结果存到第1个局部变量中,也就是V_0
13 IL_0015: ldc.i4.s 60 加载int型60
14 IL_0017: call bool ILLearn.People::IsHealthyWeight(int32,0)">调用IsHealthyWeight,因为是静态函数,所以用call
15 int32)
16 IL_001c: brtrue.s IL_0025 如果上面返回true的话就跳转到IL_0025
17 IL_001e: ldstr 加载string "not healthy"
18 IL_0023: br.s IL_002a 跳转到IL_002a
19 IL_0025: ldstr 加载string "healthy"
20 IL_002a: stloc.把结果存到第2个局部变量中,也就是V_1,IL_0017到IL_002a这几个指令加在一起用来计算三元表达式
21 IL_002b: ldstr {0} is {1}加载string "{0} is {1}"
22 IL_0030: ldloc.加载第1个局部变量
23 IL_0031: ldloc.加载第2个局部变量
24 IL_0032: call string [mscorlib]System.String::Format(string,0)">调用string.Format,这里也可以看到C# 6.0的语法糖 $"{vocation} is {healthStatus}",编译后的结果和以前的用法一样
25 object,128)">26 object)
27 IL_0037: call void [mscorlib]System.Console::WriteLine(调用WriteLine
28 IL_003c: call string [mscorlib]System.Console::ReadLine() 调用ReadLine
29 IL_0041: pop
30 IL_0042: ret
end of method Program::Main
很简单吧,当然,这个例子也很简单,没有事件,没有委托,也没有async/await之类,这些有兴趣的可以写代码跟一下,这几种都会在编译时插入也许你不知道的代码。 就这么简单学一下,应该差不多有底气和面试官吹吹牛逼了。 结束IL其实不难,有没有用则仁者见仁,智者见智,有兴趣就学一下,也花不了多少时间,确实也没必要学多深,是吧。 当然,也是要有耐心的,复杂的IL看起来还真是挺头痛。好在有工具ILSpy,可以在option里选择部分不反编译来看会比较简单些。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |