IL系列文章之三:
Array in IL
正如题目所言,这一节我们研究IL中的array(数组)。我们将看到如何定义一个数组,如何查询数组元数,使用for语句和foreach语句的异同。
先来看看我用C#写的一段程序:
using System;
class array1
{
public static void Main ()
{
int [] number;
number = new int [6];
for(int i = 0; i < 6; i++)
{
number[i] = i * i;
}
foreach(int num in number)
{
Console.WriteLine(num.ToString());
}
}
}
这段程序相信大家都看得懂吧(如果看不懂的话最好就别往下看了)。在这段程序中我们看到了C#如何定义一个数组,这和我们以前在C/C++使用的方法不太相同。C#使用了如下两句,来定义一个数组:
int [] number;
number = new int [6];
而在C/C++中使用一句
int number[6];
就够了。产生这种差异的缘由何在呢?别慌,马上就能看到。还有一点,C#中有foreach语句,这个语句与传统的for语句比较起来又如何呢?看下面,是上面的程序用ildasm反编译产生的(为了大家能看得更明白,我对反编译产生的il程序做了一些修改)。
.assembly array1{}
.class private auto ansi beforefieldinit array1
extends [mscorlib]System.Object
{
.method public static void Main() cil managed
{
.entrypoint
.maxstack 4
.locals init (int32[] V_0,//int[] number
int32 V_1,//i
int32 V_2,
int32[] V_3,//a pointer of array
int32 V_4)
ldc.i4.6//load int32 const 6(length of the array) onto the stack
newarr [mscorlib]System.Int32//number = new int[6],
//length of the array must be loaded onto the stack before
stloc.0//number
ldc.i4.0//load first local(V_0) onto the stack
stloc.1//int i = 0
br.s loopfor //goto “loopfor”, “loopfor” is only a lable
//startfor is a lable too
startfor: ldloc.0//number
ldloc.1//load V_1 onto the stack
ldloc.1
ldloc.1
mul//multiply
stelem.i4//i = i * i
ldloc.1
ldc.i4.1
add
stloc.1//i = i + 1
loopfor: ldloc.1
ldc.i4.6
blt.s startfor//if i(V_1) less than 6, goto “startfor”
ldloc.0
stloc.3//look it!!
ldc.i4.0
stloc.s V_4
br.s loopforeach
startforeach: ldloc.3
ldloc.s V_4
ldelem.i4
stloc.2
ldloca.s V_2//load address of local_2(V_3)
call instance string [mscorlib]System.Int32::ToString()
//cast int to a new string instance and stord it into local_2
call void [mscorlib]System.Console::WriteLine(string)//print it
ldloc.s V_4
ldc.i4.1
add
stloc.s V_4
loopforeach: ldloc.s V_4//index of V_3[]
ldloc.3
ldlen
conv.i4//conver len to int32
blt.s startforeach
ret//return, must exist, or else the program will stop here
} // end of method array1::Main
}
分析这段程序我们可以看出来定义一个数组的三个步骤,不知你看出了没有?
1. 定义一个数组指针,int32[] V_0
2. 将数组长度移到堆栈顶,ldc.i4.6
3. 为其分配空间,newarr [mscorlib]System.Int32
这下明白了C#中的数组为什么要通过两个步骤来定义了吧。其原因就在于在C#中我们是把数组放到托管堆上,而在C++中使用(int number[6])数组不是放在堆上的。IL能做C#不能做到的事情,如数组的下界可以不从0开始等(下次有机会写个例子J)。
为数组元素赋值,需要四个步骤,
1. 将数组指针移到栈顶,ldloc.0
2. 将数组元素的Index移到栈顶,ldloc.1
3. 将要赋的值移到栈顶(这里是同过mul运算实现),
4. 将值从栈顶移出,赋给元素,stelem.i4
选择数组元素的方法和上面差不多,只是用不着第3步了,第4步的st换成ld。
对于for语句和foreach语句其实没有本质的差异。使用foreach语句时,IL会预先产生一个数组指针int32[] V_3,在遍历数组时再把已知数组的指针赋给这个指针,以后的指令就和for语句相差无几了。
关于一维数组的用法基本上差不多了,下一次我可能要写一些关于多维数组和锯齿数组的内容。
待续……