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

简介:

        一、在.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

相关文章
|
3月前
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
56 6
|
15天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
1月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
32 2
|
1月前
|
数据库连接 开发者
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第15天】在.NET中,有两种有效的资源释放方式:一是使用`using`语句,适用于实现`IDisposable`接口的对象,如文件流、数据库连接等,能确保资源及时释放,避免泄漏;二是手动调用`Dispose`方法并处理异常,提供更灵活的资源管理方式,适用于复杂场景。这两种方式都能有效管理资源,提高应用性能和稳定性。
|
1月前
|
算法 Java 数据库连接
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第14天】在 .NET 中,`IDisposable` 接口提供了一种标准机制来释放非托管资源,如文件句柄、数据库连接等。此类资源需手动释放以避免泄漏。实现 `IDisposable` 的类可通过 `Dispose` 方法释放资源。使用 `using` 语句可确保资源自动释放。此外,.NET 的垃圾回收器会自动回收托管对象所占内存,提高程序效率。示例代码展示了如何使用 `MyFileHandler` 类处理文件操作并释放 `FileStream` 资源。
|
2月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
3月前
|
开发框架 监控 .NET
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
385 0
|
30天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
63 1