且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

由浅入深CIL系列:3.通过CIL观察.NET值类型和引用类型的内存分配

更新时间:2022-10-04 16:24:02

        一、在.NET中,内存分配是非常重要的一大块,为了更深入的了解其分配情况,本节中我们将利用一个实例来查看其CIL语言分析内存的分配情况。下面我们首先来看实例C#源码如下:


  1. class Program 
  2. static void Main(string[] args) 
  3. //将a+b+c,打印结果 
  4. int a = 3; 
  5. int b = 19; 
  6. double c = 443.25; 
  7. Console.WriteLine(a + b + c); 
  8.  
  9. //分别打印d,e,d+e 
  10. string d = "Hello World!"
  11. string e = "Print Word!"
  12. Console.WriteLine(e); 
  13. Console.WriteLine(d); 
  14. Console.WriteLine(d + e); 

        二、接下来我们看这段程序的CIL代码,通过这段代码我们大概能够猜出分别代表了什么意思。


  1. .method private hidebysig static void Main(string[] args) cil managed 
  2. //第一段声明 
  3. .entrypoint 
  4. // 代码大小 71 (0x47) 
  5. .maxstack 2 
  6. .locals init ([0] int32 a, 
  7. [1] int32 b, 
  8. [2] float64 c, 
  9. [3] string d, 
  10. [4] string e) 
  11. //第二段值类型内存存储情况 
  12. IL_0000: nop 
  13. IL_0001: ldc.i4.3 
  14. IL_0002: stloc.0 
  15. IL_0003: ldc.i4.s 19 
  16. IL_0005: stloc.1 
  17. IL_0006: ldc.r8 443.25 
  18. IL_000f: stloc.2 
  19. IL_0010: ldloc.0 
  20. IL_0011: ldloc.1 
  21. IL_0012: add 
  22. IL_0013: conv.r8 
  23. IL_0014: ldloc.2 
  24. IL_0015: add 
  25. IL_0016: call void [mscorlib]System.Console::WriteLine(float64) 
  26. //第三段引用类型内存存储情况 
  27. IL_001b: nop 
  28. IL_001c: ldstr "Hello World!" 
  29. IL_0021: stloc.3 
  30. IL_0022: ldstr "Print Word!" 
  31. IL_0027: stloc.s e 
  32. IL_0029: ldloc.s e 
  33. IL_002b: call void [mscorlib]System.Console::WriteLine(string) 
  34. IL_0030: nop 
  35. IL_0031: ldloc.3 
  36. IL_0032: call void [mscorlib]System.Console::WriteLine(string) 
  37. IL_0037: nop 
  38. IL_0038: ldloc.3 
  39. IL_0039: ldloc.s e 
  40. IL_003b: call string [mscorlib]System.String::Concat(string, 
  41. string) 
  42. IL_0040: call void [mscorlib]System.Console::WriteLine(string) 
  43. IL_0045: nop 
  44. IL_0046: ret 
  45. } // end of method Program::Main 

        首先我们看第一段CIL代码所示,声明了程序的进入点,以及定义了5个局部的变量其索引值分别为0,1,2,3,4,变量名为a,b,c,d,e。

 


  1. .entrypoint //定义了程序的进入点 
  2. // 代码大小 71 (0x47) //表明代码总共大小71个字节 
  3. .maxstack 2 
  4. .locals init ([0] int32 a, //在索引的0,1,2,3,4处定义了5个局部变量 
  5. [1] int32 b, 
  6. [2] float64 c, 
  7. [3] string d, 
  8. [4] string e) 

         其次我们来看第二段CIL代码,这是值类型的直接存储在栈中的数据,直接取出相加即可。

 


  1. //第二段值类型内存存储情况 
  2. IL_0000: nop 
  3. //int a = 3; 
  4. //将整数值 3 作为 int32 推送到计算堆栈上 
  5. IL_0001: ldc.i4.3 
  6. //将堆栈顶部的3弹出并且存储到索引为1处得局部变量b 
  7. IL_0002: stloc.0 
  8. //int b = 19; 
  9. //将整数19作为int32推送到计算机堆栈上 
  10. IL_0003: ldc.i4.s 19 
  11. //将堆栈顶部的19弹出并且存储到索引为1处得局部变量b 
  12. IL_0005: stloc.1 
  13. //double c = 443.25; 
  14. //将所提供的 float64 类型的值443.25作为 F (float) 类型推送到计算堆栈上 
  15. //从这里可以看出C# Double类型==MSIL里面的float64类型 
  16. IL_0006: ldc.r8 443.25 
  17. //将堆栈顶部的443.25弹出并且存储到索引为2处得局部变量b 
  18. IL_000f: stloc.2 
  19. //a+b 
  20. //将索引 0 处的局部变量a值3加载到计算堆栈上 
  21. IL_0010: ldloc.0 
  22. //将索引 1 处的局部变量b值19加载到计算堆栈上 
  23. IL_0011: ldloc.1 
  24. //将两个值相加并将结果推送到计算堆栈上,结果为22 
  25. IL_0012: add 
  26. //a+b得到的值22+c 
  27. //将位于计算堆栈顶部的值22转换为 float64 
  28. IL_0013: conv.r8 
  29. //将索引 2 处的局部变量b值443.25加载到计算堆栈上 
  30. IL_0014: ldloc.2 
  31. //将两个值相加并将结果465.25推送到计算堆栈上 
  32. IL_0015: add 
  33. //调用mscorlib程序集内的函数打印值465.25 
  34. IL_0016: call void [mscorlib]System.Console::WriteLine(float64) 

启发:

1.在.NET的CIL语言中首先建立一个变量的索引集合。然后在每次初始化值类型的时候先将值类型的值推到计算堆栈上,然后马上将堆栈上顶部的对应值存到对应的索引项中。

2.值类型并没有入堆,而是直接在栈上使用。

3.C#中的Double类型在CIL中实质上就是float64类型,且int32类型和float64类型一起做运算的时候,需要先将int32类型转为float64类型。

        再次我们看以下代码,以观察引用类型在内存中的存储方式和使用方法。

 


  1. //第三段引用类型内存存储情况 
  2. IL_001b: nop 
  3. //为Hello World!字符串分配内存,并且推送其对象引用到计算堆栈上。 
  4. IL_001c: ldstr "Hello World!" 
  5. //将堆栈顶部的Hello World!字符串的引用弹出并且存储到索引为3处得局部变量d 
  6. IL_0021: stloc.3 
  7. //为Print Word!字符串分配内存,并且推送其对象引用到计算堆栈上。 
  8. IL_0022: ldstr "Print Word!" 
  9. //将堆栈顶部的Print Word!字符的引用弹出并且存储到索引为4处的局部变量e 
  10. IL_0027: stloc.s e 
  11. //将索引 4 处的局部变量Print Word!字符串加载到计算堆栈上并且打印出来 
  12. IL_0029: ldloc.s e 
  13. IL_002b: call void [mscorlib]System.Console::WriteLine(string
  14.  
  15. IL_0030: nop 
  16. //将索引 3 处的局部变量Hello World!字符串加载到计算堆栈上并且打印出来 
  17. IL_0031: ldloc.3 
  18. IL_0032: call void [mscorlib]System.Console::WriteLine(string
  19. IL_0037: nop 
  20. //将d和e的字符串提取出来并且调用[mscorlib]System.String::Concat(string,string) 
  21. //对两个string进行值拷贝相加,然后将其引用存到栈顶 
  22. IL_0038: ldloc.3 
  23. IL_0039: ldloc.s e 
  24. IL_003b: call string [mscorlib]System.String::Concat(string
  25. string
  26. //将存在栈顶的新的string弹出显示出来 
  27. IL_0040: call void [mscorlib]System.Console::WriteLine(string
  28. IL_0045: nop 
  29. IL_0046: ret 

启发:

1.string是一种特殊的引用类型。

2.string类型的字符相加操作是使用[mscorlib]System.String::Concat(string,string)对值的拷贝实现的。

3.引用类型是先将其值在内存堆中分配好之后才返回其引用到栈上面。

        本节通过CIL语言观察到.NET的值类型和引用类型在内存堆和栈中是如何进行访问和管理的。以及特殊的string引用类型。



本文转自程兴亮 51CTO博客,原文链接:http://blog.51cto.com/chengxingliang/826589