由浅入深CIL系列:3.通过CIL观察.NET值类型和引用类型的内存分配
一、在.NET中,内存分配是非常重要的一大块,为了更深入的了解其分配情况,本节中我们将利用一个实例来查看其CIL语言分析内存的分配情况。下面我们首先来看实例C#源码如下:
class Program{
static void Main(string[] args)
{
//将a+b+c,打印结果
int a = 3;
int b = 19;
double c = 443.25;
Console.WriteLine(a + b + c);
//分别打印d,e,d+e string d = "Hello World!";
string e = "Print Word!";
Console.WriteLine(e);
Console.WriteLine(d);
Console.WriteLine(d + e);
}
}
二、接下来我们看这段程序的CIL代码,通过这段代码我们大概能够猜出分别代表了什么意思。
.method private hidebysig static void Main(string[] args) cil managed{
//第一段声明 .entrypoint // 代码大小 71 (0x47) .maxstack 2 .locals init ([0] int32 a, [1] int32 b, [2] float64 c, [3] string d, [4] string e)//第二段值类型内存存储情况 IL_0000: nop IL_0001: ldc.i4.3 IL_0002: stloc.0 IL_0003: ldc.i4.s 19 IL_0005: stloc.1 IL_0006: ldc.r8 443.25 IL_000f: stloc.2 IL_0010: ldloc.0 IL_0011: ldloc.1 IL_0012: add IL_0013: conv.r8 IL_0014: ldloc.2 IL_0015: add IL_0016: call void [mscorlib]System.Console::WriteLine(float64)//第三段引用类型内存存储情况 IL_001b: nop IL_001c: ldstr "Hello World!" IL_0021: stloc.3 IL_0022: ldstr "Print Word!" IL_0027: stloc.s e IL_0029: ldloc.s e IL_002b: call void [mscorlib]System.Console::WriteLine(string) IL_0030: nop IL_0031: ldloc.3 IL_0032: call void [mscorlib]System.Console::WriteLine(string) IL_0037: nop IL_0038: ldloc.3 IL_0039: ldloc.s e IL_003b: call string [mscorlib]System.String::Concat(string, string) IL_0040: call void [mscorlib]System.Console::WriteLine(string) IL_0045: nop IL_0046: ret} // end of method Program::Main 首先我们看第一段CIL代码所示,声明了程序的进入点,以及定义了5个局部的变量其索引值分别为0,1,2,3,4,变量名为a,b,c,d,e。
.entrypoint //定义了程序的进入点 // 代码大小 71 (0x47) //表明代码总共大小71个字节 .maxstack 2 .locals init ([0] int32 a, //在索引的0,1,2,3,4处定义了5个局部变量 [1] int32 b, [2] float64 c, [3] string d, [4] string e) 其次我们来看第二段CIL代码,这是值类型的直接存储在栈中的数据,直接取出相加即可。
//第二段值类型内存存储情况 IL_0000: nop //int a = 3; //将整数值 3 作为 int32 推送到计算堆栈上 IL_0001: ldc.i4.3 //将堆栈顶部的3弹出并且存储到索引为1处得局部变量b IL_0002: stloc.0 //int b = 19; //将整数19作为int32推送到计算机堆栈上 IL_0003: ldc.i4.s 19 //将堆栈顶部的19弹出并且存储到索引为1处得局部变量b IL_0005: stloc.1 //double c = 443.25; //将所提供的 float64 类型的值443.25作为 F (float) 类型推送到计算堆栈上 //从这里可以看出C# Double类型==MSIL里面的float64类型 IL_0006: ldc.r8 443.25 //将堆栈顶部的443.25弹出并且存储到索引为2处得局部变量b IL_000f: stloc.2//a+b //将索引 0 处的局部变量a值3加载到计算堆栈上 IL_0010: ldloc.0 //将索引 1 处的局部变量b值19加载到计算堆栈上 IL_0011: ldloc.1 //将两个值相加并将结果推送到计算堆栈上,结果为22 IL_0012: add//a+b得到的值22+c //将位于计算堆栈顶部的值22转换为 float64 IL_0013: conv.r8 //将索引 2 处的局部变量b值443.25加载到计算堆栈上 IL_0014: ldloc.2 //将两个值相加并将结果465.25推送到计算堆栈上 IL_0015: add //调用mscorlib程序集内的函数打印值465.25 IL_0016: call void [mscorlib]System.Console::WriteLine(float64)启发:
1.在.NET的CIL语言中首先建立一个变量的索引集合。然后在每次初始化值类型的时候先将值类型的值推到计算堆栈上,然后马上将堆栈上顶部的对应值存到对应的索引项中。
2.值类型并没有入堆,而是直接在栈上使用。
3.C#中的Double类型在CIL中实质上就是float64类型,且int32类型和float64类型一起做运算的时候,需要先将int32类型转为float64类型。
再次我们看以下代码,以观察引用类型在内存中的存储方式和使用方法。
//第三段引用类型内存存储情况 IL_001b: nop //为Hello World!字符串分配内存,并且推送其对象引用到计算堆栈上。 IL_001c: ldstr
补充:综合编程 , 其他综合 ,