|
在这篇文章里,我们将讨论IL代码是怎样处理C#中的循环。例子还涉及到数组处理,以及一些新涉及到的指令。虽然已经有人进行过相关问题的研究,我也看过几篇有关文章,不过我认为他们描述得并不是很清楚,所以在这里我借机重新整理成文,希望对大家学习理解。net会有所帮助,同时也希望对研究虚拟机机制的有关设计人员有所帮助。
同样,这里也先给出C#代码,然后再让我们详细研究其编译后的IL代码。下面是C#代码,它含有三个循环,分别是for、while、foreach循环:
public int LoopTest()
{
int i=3;
int j=9;
int s=0;
int k; file://以上各条语句定义变量并进行初始化
for(k=0;k<=i;k++)
{
s+=k;
} file://for循环块
k=0;
while(k<j)
{
s+=k;
k++;
} file://while循环块
int[] array={2,3,4,5,6,7,8,9};
foreach(int a in array)
{
s+=a;
} file://foreach循环块
return s;
}
在这里,我们要做的是搞清楚C#编译器是把源程序翻译成怎样的IL代码以实现循环处理的,或者说如何用IL语言实现C#语言中的循环。这对我们深入理解C#语言特性是很有帮助的。当然仅仅这一点还不够,以后我还会介绍更多的有关方面的问题。
首先让我们看看这个函数被编译成什么样的IL代码:
.method public hidebysig instance int32 LoopTest() cil managed
{
// 代码大小 101 (0x65)
.maxstack 3
.locals init ([0] int32 i,
[1] int32 j,
[2] int32 s,
[3] int32 k,
[4] int32[] 'array',
[5] int32 a,
[6] int32 CS$00000003$00000000,
file://跟函数返回值类型相同的局部变量,由编译器维护,专门用于存储返回
//值。如果函数为void型,则无此变量。
[7] int32[] CS$00000007$00000001,
file://局部变量,存储数组引用,用于foreach循环。本例中对应‘array’数组。
[8] int32 CS$00000008$00000002
file://局部变量,存储数组索引。专用于foreach循环,由编译器维护。
)
IL_0000: ldc.i4.3
IL_0001: stloc.0
IL_0002: ldc.i4.s 9
IL_0004: stloc.1
IL_0005: ldc.i4.0
IL_0006: stloc.2
IL_0007: ldc.i4.0
IL_0008: stloc.3
IL_0009: br.s IL_0013
IL_000b: ldloc.2
IL_000c: ldloc.3
IL_000d: add
IL_000e: stloc.2
IL_000f: ldloc.3
IL_0010: ldc.i4.1
IL_0011: add
IL_0012: stloc.3
IL_0013: ldloc.3
IL_0014: ldloc.0
IL_0015: ble.s IL_000b
IL_0017: ldc.i4.0
IL_0018: stloc.3
IL_0019: br.s IL_0023
IL_001b: ldloc.2
IL_001c: ldloc.3
IL_001d: add
IL_001e: stloc.2
IL_001f: ldloc.3
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.3
IL_0023: ldloc.3
IL_0024: ldloc.1
IL_0025: blt.s IL_001b
IL_0027: ldc.i4.8
IL_0028: newarr [mscorlib]System.Int32
file://创建长度为8的System.Int32数组。可以看出数组元素被映射到Int32类对象。
IL_002d: dup
IL_002e: ldtoken field valuetype '<PrivateImplementationDetails>'/'$$struct0x6000002-1'
'<PrivateImplementationDetails>'::'$$method0x6000002-1'
IL_0033: call void [mscorlib] System.Runtime.CompilerServices.RuntimeHelpers::
InitializeArray(class[mscorlib]System.Array, valuetype [mscorlib] System.RuntimeFieldHandle)
IL_0038: stloc.s 'array'
IL_003a: ldloc.s 'array'
IL_003c: stloc.s CS$00000007$00000001
IL_003e: ldc.i4.0
IL_003f: stloc.s CS$00000008$00000002
IL_0041: br.s IL_0055
IL_0043: ldloc.s CS$00000007$00000001
IL_0045: ldloc.s CS$00000008$00000002
IL_0047: ldelem.i4
IL_0048: stloc.s a
IL_004a: ldloc.2
IL_004b: ldloc.s a
IL_004d: add
IL_004e: stloc.2
IL_004f: ldloc.s CS$00000008$00000002
IL_0051: ldc.i4.1
IL_0052: add
IL_0053: stloc.s CS$00000008$00000002
IL_0055: ldloc.s CS$00000008$00000002
IL_0057: ldloc.s CS$00000007$00000001
IL_0059: ldlen
IL_005a: conv.i4
IL_005b: blt.s IL_0043
IL_005d: ldloc.2
IL_005e: stloc.s CS$00000003$00000000
IL_0060: br.s IL_0062
IL_0062: ldloc.s CS$00000003$00000000
IL_0064: ret
} // end of method Advanced::LoopTest
关于函数话题如.locals init语句等,请参见文章〈函数相关〉。这里我对其中的一些指令做出解释,主要是与本文相关的条件转移指令(b*.s)等。其他指令以后我会作适当的介绍。如下所示:
指令 意义 记忆方法(*)
br.s绝对跳转,相当于jmp
blt.s小于转 Lower Than
ble.s小于等于转 Lower or Equals
ldlen取得数组长度
ldelem.i4根据索引取得数组项
这里我们可以看到 .locals init伪指令给出了同源程序相同变量名称。这是因为在反汇编时,相同目录下有调试信息文件(*.pdb),否则的我们看到的结果变量以V_x形式(如V_1、V_2等)表示。有关函数局部变量的话题,请参见《函数相关》一文。
如果你有WIN32汇编程序设计经验,可能都熟悉怎样实现循环控制。 |
|