在IL中,标号(label)是一个末尾带有冒号(即:)的名称。它使咱们可以从代码的一部分无条件地跳转到另外一部分。咱们常常在由反编译器生成的IL代码中看到这个标号。例如:程序员
IL_0000: ldstr "hi"编程
IL_0005: call void [mscorlib]System.Console::WriteLine(class System.String)编程语言
IL_000a: call void zzz::abc()ide
IL_000f: ret函数
在冒号前面的词就是标号。在下面给出的程序中,咱们在函数abc中建立一个名为a2的标号。指令br用于随时跳转到程序中的任何标号。oop
a.ilspa
.assembly mukhi {}设计
.class private auto ansi zzz extends System.Objectci
{作用域
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0,class zzz V_1)
newobj instance void zzz::.ctor()
stloc.1
call int32 zzz::abc()
stloc.0
ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
ret
}
.method private hidebysig static int32 abc() il managed
{
.locals (int32 V_0)
ldc.i4.s 20
br.s a2
ldc.i4.s 30
a2: ret
}
}
Output
20
函数abc示范了这个概念。在这个函数中,代码绕过了指令ldc.i4.s 30。所以,返回值显示为20而不是30。从而,IL使用br指令来无条件地跳跃到代码的任何部分。(程序集指令br获取4字节,而在.sr以前的br,即br.s获取1字节,对于每一个标记为.s的指令,解释都是相同的。)
br指令是IL得以运转的关键组件之一。
a.cs
class zzz
{
static bool i = true;
public static void Main()
{
if (i)
System.Console.WriteLine("hi");
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld bool zzz::i
brfalse.s IL_0011
ldstr "hi"
call void [mscorlib]System.Console::WriteLine(class System.String)
IL_0011: ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.1
stsfld bool zzz::i
ret
}
}
Output
hi
在咱们的C#程序中,咱们将静态变量初始化为true值。
l 静态变量,若是它们是字段,就会在静态构造函数.cctor中被初始化。这会在上面的程序中显示。
l 另外一方面,局部变量在它们所在的函数中被初始化。
这里,让人吃惊的是,使用ldc指令将值1放置在栈上的静态构造函数中。即便同时在C#和IL中定义了字段i,仍是没有true或false这样的符号。
接下来,使用stsfld将静态变量i初始化为值1,尽管变量是布尔类型的。这就证明了IL支持bool数据类型,它不会识别出单词true或false。所以,在IL中,布尔值分别只是数字1或0的别名。
布尔运算符TRUE或FALSE是由C#引进的关键字,用来使程序员的工做更加轻松。因为IL不直接支持这些关键字,因此它会替代地使用数字1或0。
指令ldsfld把静态变量的值加载到栈上。指令brfalse对栈进行扫描。若是它找到了数字1,它就会将其解释为TRUE,而若是它找到了数字0,它就会将其解释为FALSE。
在这个例子中,它在栈上找到的值是1或TRUE,因此它不会跳转到标号IL_0011。在从C#到IL的转换中,ildasm使用以IL_开始的名称来代替标号。
指令brfalse表示“若是FALSE就跳转到标号”。这不一样于br,后者老是会致使一个跳转。从而,brfalse是一个有条件的跳转指令。
在IL中没有提供if语句功能的指令。C#中的if语句会被转换为IL中的转移(branch)指令。咱们所处的任何汇编器,都没有像if结构体这样的高级概念。
能够看到,咱们刚刚学到的那些知识,对于咱们掌握IL是很是重要的。这将帮助咱们得到——区别关于哪一个概念是IL的一部分而哪些是由编程语言的设计者引进——的能力
尤为须要注意的是,若是IL不支持某个特性,它就不能用任何.NET编程语言实现。从而,熟悉IL所支持的各类概念的重要性——怎么强调都不过度。
a.cs
class zzz
{
static bool i = true;
public static void Main()
{
if (i)
System.Console.WriteLine("hi");
else
System.Console.WriteLine("false");
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld bool zzz::i
brfalse.s IL_0013
ldstr "hi"
call void [mscorlib]System.Console::WriteLine(class System.String)
br.s IL_001d
IL_0013: ldstr "false"
call void [mscorlib]System.Console::WriteLine(class System.String)
IL_001d: ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.1
stsfld bool zzz::i
ret
}
}
Output
hi
在编程语言中,if-else语句是极其容易理解的,可是在IL中它倒是至关使人困惑的。IL检查栈上的值是1仍是0。
l 若是栈上的值是1,正如这个例子中的那样,它调用带有参数hi的WriteLine函数,并随后使用无条件跳转指令br,跳转到标号IL_001d。
l 若是栈上的值是0,代码跳转到IL_0013,而且WriteLine函数会打印出false。
从而,为了在IL中实现if-else结构,须要一个有条件跳转和一个无条件跳转。若是咱们使用多个if-else语句,那么IL代码的复杂度就会动态增长。
如今,能够看出编译器的编写者的智商了。
a.cs
class zzz
{
public static void Main()
{
}
void abc( bool a)
{
if (a)
{
int i = 0;
}
if ( a)
{
int i = 3;
}
}
}
a.il
.assembly mukhi {}
.class public auto ansi zzz extends [mscorlib]System.Object
{
.field private int32 x
.method public hidebysig static void vijay() il managed
{
.entrypoint
ret
}
.method private hidebysig instance void abc(bool a) il managed
{
.locals (int32 V_0,int32 V_1)
ldarg.1
brfalse.s IL_0005
ldc.i4.0
stloc.0
IL_0005: ldarg.1
brfalse.s IL_000a
ldc.i4.3
stloc.1
IL_000a: ret
}
}
C#编程语言就更复杂了。在内部的一组括号中,咱们不能建立以前已经在外部建立的变量。上面的C#程序在语法上是正确的,由于括号都是在同一级别上。
在IL中,会稍微简单一些。这两个i会变成两个单独的变量V_0和V_1。所以,IL不会暴露施加在变量上的任何约束。
a.cs
class zzz
{
static bool i = true;
public static void Main()
{
while (i)
{
System.Console.WriteLine("hi");
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.method public hidebysig static void vijay() il managed
{
.entrypoint
br.s IL_000c
IL_0002: ldstr "hi"
call void [mscorlib]System.Console::WriteLine(class System.String)
IL_000c: ldsfld bool zzz::i
brtrue.s IL_0002
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.1
stsfld bool zzz::i
ret
}
}
当看到反汇编的代码时,你将理解为何程序员不以编写IL代码来谋生。即便一个简单的while循环,在转换为IL后都会变得惊人的复杂。
对于一个while结构,会建立一个到标号IL_000c的无条件跳转,它位于函数的结尾。这里,它加载静态变量i的值到栈上。
下一个指令brtrue,作的事情和指令brfalse所作的正好相反。实现以下:
l 若是栈顶的值——例如,字段i的值——是1,那么它会跳转到标号IL_0002。而后值hi被放到栈上而且WriteLine函数会被调用。
l 若是栈顶的值是0,那么程序将跳转到ret指令。
上面的程序,正如你所看到的那样,并不打算中止。它会继续流动,就像一个起源于一个巨大冰川的水流。
a.cs
class zzz
{
static int i = 2;
public static void Main()
{
i = i + 3;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static int32 i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::i
ldc.i4.3
add
stsfld int32 zzz::i
ldsfld int32 zzz::i
call void [mscorlib]System.Console::WriteLine(int32)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.2
stsfld bool zzz::i
ret
}
}
Output
5
IL没有操做符用来作两个数字的加法,而是使用add指令。
add指令须要用来作加法的两个数字,也就是栈上开始的2个有效的元素。所以,ldsfld指令把静态变量i的值和常量值3放到栈上。随后,add指令把它们相加并把结果放到栈上。它还会从栈上移除用来作加法的2个数字。
一旦指令被执行了,IL中的大多数指令就会摆脱栈上的参数,也就是该指令要操做的参数。
使用指令stsfld将静态变量i初始化为加法的结果总和。剩下的代码直接显示了变量i的值。
在IL中没有++操做符的等价物。它会被转换为指令ldc.i4.1。一样,两个数字相乘,须要使用mul指令;相减,就使用sub指令,等等。它们在IL中都有等价物。以后的代码保持不变。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j > 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
cgt
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
True
如今咱们将研究IL如何处理条件操做符。让咱们考虑C#中的语句j>i16。IL首先把值j放到栈上,也就是常量值16的前面。随后它会调用cgt操做,这是它首次在咱们的源代码中出现。这个指令检查栈上的第1个值是否大于第2个。若是是,那么它会把值1(TRUE)放到栈上,不然它会把值0(FALSE)放到栈上。这个值随后被存储到变量i中。使用WritleLine函数,就会生成布尔值的输出,从而咱们看到显示True。
一样,操做符<被转换为clt指令,它会检查栈上的第1个值是否小于第2个。从而咱们看到IL具备它本身的一套逻辑操做符,对基本的逻辑运算进行内部处理。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j == 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
ceq
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
False
操做符==就是EQUALITY操做符。它也须要栈上的2个操做数(operand)来检查相等性。此后它使用ceq指令来检查相等性。若是相等,它会把值1(TRUE)放到栈上,若是不相等,它会把值0(FALSE)放到栈上。指令ceq是IL的逻辑指令集的不可缺乏的一部分。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j >= 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
cgt
ldc.i4.0
ceq
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
False
“小于等于”(<=)和“大于等于”(>=)的实现有一点复杂。实际上它们都具备2个融合在一块儿的条件。
在>=的状况中,IL首先使用cgt指令来检查第1个数字是否大于第2个数字。若是是,它将返回值1,不然就返回值0。若是第1个条件是FALSE,那么ceq指令将会检查这2个数字是否相等。若是相等,它就返回TRUE,不然就返回FALSE。
让咱们从一个稍微不一样的角度来分析上面的IL代码。咱们对这两个值19和16进行比较。在这个例子中,由于19大于16,因此指令cgt将会把值1放到栈上。
让咱们在静态构造函数中将字段j的值修改成1。如今,因为数字1不大于16,因此cgt指令将把值FALSE或0放到栈上。此后,使用ldc指令把另外一个0放到栈上。如今当ceq指令比较两个值时,由于它们都是0,因此它会返回TRUE。
如今,若是咱们将j修改成16,cgt指令将返回FALSE,由于16不大于16。此后,因为使用ldc指令把值0放到栈上,这两个传递到ceq的值将都是0。因为0与0是相等的,因此返回值是1或TRUE。
若是你不能理解上面的解释,那么就从源代码中移除ldc.i4.0和ceq这两行,并观察输出。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j != 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
ceq
ldc.i4.0
ceq
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
True
“不等于”操做符,也就是!=,是==的相反操做。它使用了两个ceq指令。第1个ceq指令用来检查栈上的值是否相等。若是它们是相等的,它就会返回TRUE;不然就返回FALSE。
第2个ceq将前面的ceq结果和FLASE进行比较。若是第1个ceq的结果是TRUE,那么最后的答案就是FALSE,反之亦然。
这确实是一种首创的方式来对一个值求否。
a.cs
class zzz
{
static int i = 1;
public static void Main()
{
while ( i <= 2)
{
System.Console.WriteLine(i);
i++;
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static int32 i
.method public hidebysig static void vijay() il managed
{
.entrypoint
br.s IL_0018
IL_0002: ldsfld int32 zzz::i
call void [mscorlib]System.Console::WriteLine(int32)
ldsfld int32 zzz::i
ldc.i4.1
add
stsfld int32 zzz::i
IL_0018: ldsfld int32 zzz::i
ldc.i4.2
ble.s IL_0002
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 1
stsfld int32 zzz::i
ret
}
}
Output
1
2
在介绍完条件语句以后,咱们如今将关注于while循环。这种转换是必须的,由于咱们在诸如while这样的循环中使用条件语句。包括条件的while循环稍微有点复杂。
让咱们直接到标号IL_0018上,它位于IL代码中zzz函数的结尾。这里存在着一个条件。i的值(即1)被存储到栈上。接下来,常量2被放到栈上。
若是你再次访问C#代码,那么while语句中的条件就是i <= 2。指令ble.s是基于两个构造函数的:cgt和brfalse。这个指令检查了第1个值(即变量i)是否小于等于第2个值。若是是,它就会指示程序跳转到标号IL_0002。若是不是,程序就移动到下一个指令。
所以,像ble这样的指令使咱们的工做更加容易,由于咱们没必要再次使用cgt和brfalse指令。
在C#中,while结构的条件出如今顶部,可是条件的代码出如今底部。在转换为IL时,在while结构体之间被执行的代码,会被放置在条件代码之上。
a.cs
class zzz
{
static int i = 1;
public static void Main()
{
for ( i = 1; i <= 2 ; i++)
{
System.Console.WriteLine(i);
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static int32 i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldc.i4.1
stsfld int32 zzz::i
br.s IL_001e
IL_0008: ldsfld int32 zzz::i
call void [mscorlib]System.Console::WriteLine(int32)
ldsfld int32 zzz::i
ldc.i4.1
add
stsfld int32 zzz::i
IL_001e: ldsfld int32 zzz::i
ldc.i4.2
ble.s IL_0008
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 1
stsfld int32 zzz::i
ret
}
}
Output
1
2
老生常谈,while和for结构提供了相同的功能,能够互相转换。
在for循环中,第1个分号以前的代码只能被执行一次。所以,将要被初始化的变量i,位于循环的外面。而后,咱们无条件跳转到标号IL_001e,以检查i的值是否小于2。若是结果为TRUE,那么代码跳转到标号IL_0008,它是for语句这段代码的开始点。
使用WriteLine函数打印出i的值。此后,变量i的值每次增加1,而条件会被一次又一次的检查。
a.cs
public class zzz
{
public static void Main()
{
int i;
i = 1;
while ( i <= 2)
{
System.Console.Write(i);
i++;
}
i = 1;
do
{
System.Console.Write(i);
i++;
} while ( i <= 2);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0)
ldc.i4.1
stloc.0
br.s IL_000e
IL_0004: ldloc.0
call void [mscorlib]System.Console::Write(int32)
ldloc.0
ldc.i4.1
add
stloc.0
IL_000e: ldloc.0
ldc.i4.2
ble.s IL_0004
ldc.i4.1
stloc.0
IL_0014: ldloc.0
call void [mscorlib]System.Console::Write(int32)
ldloc.0
ldc.i4.1
add
stloc.0
ldloc.0
ldc.i4.2
ble.s IL_0014
ret
}
}
Output
1212
在C#中,do循环和while循环之间的区别是——条件在什么位置被检查。
l 在do-while循环中,条件会在循环的结尾被检查。这意味着循环中的代码至少会被调用一次。
l 在while循环中,条件会在循环的开始被检查。所以,代码可能历来都不会被执行。
不论是哪一种状况,咱们都把值1放到栈上,并初始化变量i或V_1。
l 在while循环中,咱们首先跳转到标号IL_000e,也就是检查条件——变量是否小于等于2——的地方。若是返回TRUE,咱们就跳转到标号IL_0004。
l 在do-while循环中,首先Write函数会被执行,随后包括在花括号{}中的剩余代码将会被执行。当到达花括号{}中代码的最后一行时,条件才会被检查。
所以,在IL中写一个do-while循环要比写一个while循环简单得多,由于条件会直接在循环的末尾被检查。
a.cs
public class zzz
{
public static void Main()
{
int i ;
for ( i = 1; i<= 10 ; i++)
{
if ( i == 2)
break;
System.Console.WriteLine(i);
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0)
ldc.i4.1
stloc.0
br.s IL_0014
IL_0004: ldloc.0
ldc.i4.2
bne.un.s IL_000a
br.s IL_0019
IL_000a: ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
ldloc.0
ldc.i4.1
add
stloc.0
IL_0014: ldloc.0
ldc.i4.s 10
ble.s IL_0004
IL_0019: ret
}
}
Output
1
break语句强迫退出for循环、while循环或do-while循环。和往常同样,咱们跳转到标号IL_0014,也就是变量V_0或i的值被放置到栈上的地方。而后,咱们把条件值10放到栈上,并使用ble.s指令检查i是小于仍是大于10。
a.cs
public class zzz
{
public static void Main()
{
int i ;
for ( i = 1; i<= 10 ; i++)
{
if ( i == 2)
continue;
System.Console.WriteLine(i);
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0)
ldc.i4.1
stloc.0
br.s IL_0014
IL_0004: ldloc.0
ldc.i4.2
bne.un.s IL_000a
br.s IL_0010
IL_000a: ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
IL_0010: ldloc.0
ldc.i4.1
add
stloc.0
IL_0014: ldloc.0
ldc.i4.s 10
ble.s IL_0004
ret
}
}
continue语句控制for循环到达结束位置。当if语句结果为TRUE时,程序将绕过WriteLine函数而跳转到循环的结束。而后,代码将在标号IL_0010继续执行,这里,变量V_0的值会增长1。
在break和continue语句之间的主要区别以下所示:
l 在break语句中,程序会跳出循环。
l 在continue语句中,程序绕过剩下的语句,跳转到循环的结尾。
goto语句也能到达相同的功能。从而,break语句、continue语句或goto语句,在转换为IL时,都会被转换为相同的br指令。
下面的程序示范了C#中的goto语句会被直接转换为IL中的br语句。
a.cs
public class zzz
{
public static void Main()
{
goto aa;
aa: ;
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
br.s IL_0002
IL_0002: ret
}
}
C#中一个简单的goto语句,会被转换为IL中的br指令。在C#这样的语言中使用goto被认为是不恰当的,可是,它在IL中的等价物——br指令,对于实现诸如if语句、循环等各类结构而言,倒是极其有用的。所以,在编程语言中的禁忌,在IL中倒是极其有用的。
a.cs
public class zzz
{
public static void Main()
{
int j;
for ( int i = 1; i <= 2 ; i++)
System.Console.Write(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0,int32 V_1)
ldc.i4.1
stloc.1
br.s IL_000e
IL_0004: ldloc.1
call void [mscorlib]System.Console::Write(int32)
ldloc.1
ldc.i4.1
add
stloc.1
IL_000e: ldloc.1
ldc.i4.2
ble.s IL_0004
ret
}
}
Output
12
这个例子解释了for循环。咱们在Main函数中建立了一个变量j,在for语句中建立了一个变量i。在C#中,这个变量i只在for循环中是可见的。所以,这个变量的做用域是受限制的。
可是,转换到IL时,全部的变量都具备相同的做用域。这是由于,变量做用域的概念对IL而言有所不一样。所以,取决于C#编译器所执行的变量做用域规则。所以,咱们能判断出——全部的变量在IL中具备相同的做用域和可见性。