现在的位置: 首页 > 综合 > 正文

第三章 选择和循环

2013年09月15日 ⁄ 综合 ⁄ 共 5781字 ⁄ 字号 评论关闭

-3-

 

IL中,标号(label)是一个末尾带有冒号(即:)的名称。它使我们能够从代码的一部分无条件地跳转到另一部分。我们经常在由反编译器生成的IL代码中看到这个标号。例如:

IL_0000: ldstr      "hi"

IL_0005: call       void [mscorlib]System.Console::WriteLine(class System.String)

IL_000a: call       void zzz::abc()

IL_000f: ret

           

在冒号前面的词就是标号。在下面给出的程序中,我们在函数abc中创建一个名为a2的标号。指令br用于随时跳转到程序中的任何标号。

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.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值。

静态变量,如果它们是字段,就会在静态构造函数.cctor中被初始化。这会在上面的程序中显示。

另一方面,局部变量在它们所在的函数中被初始化。

这里,让人吃惊的是,使用ldc指令将值1放置在栈上的静态构造函数中。即使同时在C#IL中定义了字段i,还是没有truefalse这样的符号。

接下来,使用stsfld将静态变量i初始化为值1,尽管变量是布尔类型的。这就证实了IL支持bool数据类型,它不会识别出单词truefalse。因此,在IL中,布尔值分别只是数字10的别名。

布尔运算符TRUEFALSE是由C#引进的关键字,用来使程序员的工作更加轻松。由于IL不直接支持这些关键字,所以它会替代地使用数字1或0。

指令ldsfld把静态变量的值加载到栈上。指令brfalse对栈进行扫描。如果它找到了数字1,它就会将其解释为TRUE,而如果它找到了数字0,它就会将其解释为FALSE

在这个例子中,它在栈上找到的值是1TRUE,所以它不会跳转到标号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

如果栈上的值是1,正如这个例子中的那样,它调用带有参数hiWriteLine函数,并随后使用无条件跳转指令br,跳转到标号IL_001d

如果栈上的值是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_0V_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

抱歉!评论已关闭.