由浅入深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 " Hello World! "
// 将堆栈顶部的Hello World!字符串的引用弹出并且存储到索引为3处得局部变量d
IL_0021: stloc. 3
// 为Print Word!字符串分配内存,并且推送其对象引用到计算堆栈上。
IL_0022: ldstr " Print Word! "
// 将堆栈顶部的Print Word!字符的引用弹出并且存储到索引为4处的局部变量e
IL_0027: stloc.s e
// 将索引 4 处的局部变量Print Word!字符串加载到计算堆栈上并且打印出来
IL_0029: ldloc.s e
IL_002b: call
void [mscorlib]System.Console::WriteLine( string )

IL_0030: nop
// 将索引 3 处的局部变量Hello World!字符串加载到计算堆栈上并且打印出来
IL_0031: ldloc. 3
IL_0032: call
void [mscorlib]System.Console::WriteLine( string )
IL_0037: nop
// 将d和e的字符串提取出来并且调用[mscorlib]System.String::Concat(string,string)
// 对两个string进行值拷贝相加,然后将其引用存到栈顶
IL_0038: ldloc. 3
IL_0039: ldloc.s e
IL_003b: call
string [mscorlib]System.String::Concat( string ,
string )
// 将存在栈顶的新的string弹出显示出来
IL_0040: call void [mscorlib]System.Console::WriteLine( string )
IL_0045: nop
IL_0046: ret
复制代码

启发:

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

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

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

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



本文转自程兴亮博客园博客,原文链接:http://www.cnblogs.com/chengxingliang/archive/2011/06/29/2090737.html,如需转载请自行联系原作者


相关文章
|
4月前
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
58 6
|
1月前
|
开发框架 监控 .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
|
2月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
38 2
|
2月前
|
数据库连接 开发者
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第15天】在.NET中,有两种有效的资源释放方式:一是使用`using`语句,适用于实现`IDisposable`接口的对象,如文件流、数据库连接等,能确保资源及时释放,避免泄漏;二是手动调用`Dispose`方法并处理异常,提供更灵活的资源管理方式,适用于复杂场景。这两种方式都能有效管理资源,提高应用性能和稳定性。
|
2月前
|
算法 Java 数据库连接
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第14天】在 .NET 中,`IDisposable` 接口提供了一种标准机制来释放非托管资源,如文件句柄、数据库连接等。此类资源需手动释放以避免泄漏。实现 `IDisposable` 的类可通过 `Dispose` 方法释放资源。使用 `using` 语句可确保资源自动释放。此外,.NET 的垃圾回收器会自动回收托管对象所占内存,提高程序效率。示例代码展示了如何使用 `MyFileHandler` 类处理文件操作并释放 `FileStream` 资源。
|
3月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
4月前
|
开发框架 监控 .NET
|
3月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
51 7
|
3月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
82 0