浅谈C#与C++的类型内存分配

浅谈C#与C++的类型内存分配

直到今天我做笔试题遇到问C++内存分配的时候

才发现我对C#的理解仍然是很模糊的

当然这俩的分配策略肯定是有所不同的

只是我在回想C#的时候发现我并没有办法很好的说清楚

所以打算写篇文章来简单讲讲这个东西


前置知识补充——内存区域划分

栈:全称应该是堆栈 为了和堆区别取了后一个字

堆:C++里就叫堆 C#比较特别 因为是托管给CLR的所以叫托管堆

总之之后我们在提到IL的时候还会提起堆栈这个词

所以这里事先说明了一下


C#类型的内存分配

值类型:分配在栈上 不受new的影响

引用类型:值(数据)在堆里 引用存放在栈里

容易混淆的例子——数组

我们知道 数组是引用类型 这点是很清楚的

但数组能存的东西就不固定了

现在有几个数组

int[] nums

object[] objs

string[] strs

请问数组本身的内存分配到了哪里?

数组元素的内存分配到了哪里?

数组指向的元素的内存分配到了哪里?

答案是 引用包裹的值类型都会进入堆

上面三个 数组本身内存都在栈上

数组元素实际是一些引用 所以也存在栈上

而数组指向的真正的元素的存在堆里了

这一点同理类里面定义了几个值类型

It is frequently the case that array elements, fields of reference types, locals in an iterator block and closed-over locals of a lambda or anonymous method must live longer than the activation period of the method that first required the use of their storage. And even in the rare cases where their lifetimes are shorter than that of the activation of the method, it is difficult or impossible to write a compiler that knows that. Therefore we must be conservative: all of these storage locations go on the heap.

https://ericlippert.com/2010/09/30/the-truth-about-value-types/

数组元素 引用类型变量 迭代器中的局部变量 匿名方法的封闭局部变量

必须首先比使用它们的存储的方法生命周期更长

因为没办法找出极端用例 所以只能保守的分配

这些变量的内存都要分配在堆上

容易混淆的例子——结构体

结构体是值类型 这一点是没啥问题的

当我们new一个结构体的时候 内存又分配在哪里呢?

答案是仍然在栈上

通过查看IL代码可以得到这一信息

MyClass的引用和Point结构体都在计算堆栈上

unsafe写指针来测试也可以得到同样的结果

注:数组存结构体 结构体最后也会分配在堆里

容易混淆的例子——string

string是特殊的引用类型

它的内存是分配在堆上的

string str1 = "123";
string str2 = "123";
Debug.Log(ReferenceEquals(str1, str2));

string str3 = new string("456");
string str4 = new string("456");
Debug.Log(ReferenceEquals(str3, str4));

第一个比较的结果为true

第二个比较的结果为false

这很奇怪 1和2值相同 指向了同一个对象

但3和4值也相同 指向的对象却是不同的

这就要提到CLR对于字符串做的处理了

CLR中维护了一个叫做“驻留池”的表

这个表记录了字符串实例的引用

当创建相同的字符串的时候 就不会再分配一次内存

而是把新创建的字符串地址指向和表里已有相同的数据

只有通过这种方式才会检查驻留池

当使用new声明的时候是不会检查驻留池的


C++类型的内存分配

C++可以说内存分配全靠程序员 需要靠程序员做出最佳决策

这点C#的托管可以说是省了不少事

全局变量和静态变量分配到静态存储区 这一点和C#是一样的

类型上并没有特别的区别 主要取决于手动控制

如果没有new或者malloc 那局部变量和函数参数都会分配在栈上

如果new或者malloc 那就会分配在堆上

这三点 对于指针也是成立的

指针的内存分配可以在静态区 可以在栈 可以在堆上

当然实际开发过程中会不会这么用是另一回事了

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注