基础才是重中之重~理解内存中的栈和堆
.NET中使用stack(栈)和heap(堆)两种结构在内存中存储数据,今天咱们就来说说这两个结构
- Value Types,值类型
在C#中,值类型继承自System.ValueType的,它们分别是
Bool, byte , char, decimal, double, enu, float, int, long, sbyte, short, struct, uint, ulong, ushort - Reference Types 引用类型
引用类型包括所有的从System.Object继承下来的类型,它们分别是
class, interface, delegate, object,string,其中string是一种特殊的引用类型,之后的文章中我会单独说它,.net中的gc(垃圾回收)主要是回收heap中的内存。 - 指针:在C#中它已经被遗忘,但不能不说,因为引用类型在内在中事实上是以指针的形式存储在Stack上的,而它的数据内容是在Heap上,也就是上引用类型其实是在stack上做了一个地址的引用。
Stack是自我维护的,意味着基本上不需要我们手动进行内存管理,它有自己的内存管理体系,对于window来说最高是1M,而.net最大只能用256K没有记错的话,如果超出这个范围就会出现溢出。 而heap则是我们建立object对象所存储的地方,它可以由我们自己用GC去回收也可以由.net自己去回收。
对于在教科书上的一个例子,我要说一下,就是斐波那切数列问题,一般书上都是用递归写的,这对于程序员的影响事实上是很大的,如果程序员用普通的递归写这个算法,那你的服务没准什么时候就挂了,原因是内存出现stack溢出的现象,具体的原因用“老赵”同志写了段话非常有代表性:它把普通递归改写成了尾递归解决了这个问题。
int FactorialTailRecursion(int n, int acc)
{
if (n == 0) return acc;
return FactorialTailRecursion(n - 1, acc * n); //变量仍然对程序有影响,所以编译器会一次一次堆累它使用的内存
}
改成尾递归之后:
int FactorialLoopOptimized(int n, int acc)
{
while (true)
{
if (n == 0) return acc;
acc *= n;
n--;
}
}
方法之前所积累下的各种状态对于递归调用结果已经没有任何意义,因此完全可以把本次方法中留在堆栈中的数据完全清除,把空间让给最后的递归调用。这样 的优化便使得递归不会在调用堆栈上产生堆积,意味着即时是“无限”递归也不会让堆栈溢出”。这其实才是尾递归的“正统”优化方式,那么我们先暂时忘记之前 的“循环优化”,从最简单的示例中查看这样的优化是如何进行的。
在这里,感谢老赵同志对基础的透彻讲解。