探究C#中Class和Struct互相嵌套的内存问题

  • 探究C#中Class和Struct互相嵌套的内存问题已关闭评论
  • 281 次浏览
  • A+
所属分类:.NET技术
摘要

先回顾一下C#的内存种类栈区:由编译器自动分配释放 ,存放值类型的对象本身,引用类型的引用地址(指针),静态区对象的引用地址(指针),常量区对象的引用地址(指针)等。其操作方式类似于数据结构中的栈。


内存分区

先回顾一下C#的内存种类

  1. 栈区:由编译器自动分配释放 ,存放值类型的对象本身,引用类型的引用地址(指针),静态区对象的引用地址(指针),常量区对象的引用地址(指针)等。其操作方式类似于数据结构中的栈。

  2. 堆区(托管堆):用于存放引用类型对象本身。在c#中由.net平台的垃圾回收机制(GC)管理。栈,堆都属于动态存储区,可以实现动态分配。

  3. 静态区及常量区:用于存放静态类,静态成员(静态变量,静态方法),常量的对象本身。由于存在栈内的引用地址都在程序运行开始最先入栈,因此静态区和常量区内的对象的生命周期会持续到程序运行结束时,届时静态区内和常量区内对象才会被释放和回收(编译器自动释放)。所以应限制使用静态类,静态成员(静态变量,静态方法),常量,否则程序负荷高。

  4. 代码区:存放函数体内的二进制代码。

内存分配

值类型分配在栈上,引用类型分配在堆上,这个概念一般都是比较清楚的。但是遇到下面的情况内存又是如何分配的呢?

Struct中嵌套Class对象

using System.Runtime.InteropServices;  namespace MyNamespace {     namespace MyNamespace {     public class B     {         public int x;         public byte y;     }      public struct A     {         public byte x;         public B b;     }      public class Test     {         static A a;         public static void Main()         {             a = new A()             {                 x = 1,                 b = new B() { x = 1, y = 1 },             };              System.Console.WriteLine(Marshal.SizeOf(a));         }     } } 

思考一下打印出来的值会是多少?

32位系统:
探究C#中Class和Struct互相嵌套的内存问题

64位:
探究C#中Class和Struct互相嵌套的内存问题

原因:

  1. 不同的编译器(x86、x64)对应的指针大小不一样。x86指针大小4个字节,x64指针大小为8个字节
  2. struct中存储Class,存储的是类的指针引用。整个Struct开辟在栈上,但是真正的类内存依旧是开辟在堆上。
  3. struct存在内存对齐的问题(提高访问速度)。内存对齐会向内存最大的那个对齐,即测试代码中,A.x会向(A.b的指针)大小对齐。

结论:Struct中嵌套Class引用对象,持有的是Class的引用指针。

Claas中嵌套Struct

using System.Runtime.InteropServices;  namespace MyNamespace {     public class B     {         public int x;         public A y;     }      public struct A     {         public int x;         public byte y;     }      public class Test     {         static B b;         public static void Main()         {             b = new B()             {                 x = 1,                 y = new A() { x = 1, y = 1 },             };         }     } } 

通过对堆内存进行快照

32位:
探究C#中Class和Struct互相嵌套的内存问题

64位:
探究C#中Class和Struct互相嵌套的内存问题

出乎意料的内存大小!为什么会是这样的内存大小,我们先研究一下如果仅仅只有一个空对象,这个对象在内存中的大小是多少呢?我们通过查阅.Net官方文档看一下,一个对象是如何分配的:
探究C#中Class和Struct互相嵌套的内存问题

引用类型变量(如smallObj)以固定大小(4字节)存储在栈上,并指向在托管堆上分配的对象实例的地址。 smallObj的实例包含指向相应类型的MethodTable的TypeHandle(类型对象指针)和syncblk index(同步块索引,用来做线程同步的,这里就不详细讲了,大家可以去原文查看)。最后当一个类没有定义任何实例字段,它将产生4个字节的开销(用于分配到栈上来对他进行引用)。这样我们就可以算出32位下一个空对象的内存大小了:4(syncblk index)+ 4(TypeHandle)+ 4(Instance Fields)= 12个字节。同理在64位下将会是24个字节。


返回到上面的测试用例,这时候我们就明白为什么是这样的内存分配大小了。32位下:基础的12个字节 + 4字节(int)+ 4字节(Struct指针) = 20个字节。64位下的32个字节留给大家思考是怎么样一个组成的呢?

总结

  • Struct内部持有的是Class的指针
  • Class内部持有的也是Struct的指针,但是这些都是开辟在堆上的
  • Class需要分配syncblk index和TypeHandle,用来进行同步索引和类型查询,在考虑Class的内存开销的时候需要考虑进去。

探究C#中Class和Struct互相嵌套的内存问题