关于内存的使用,栈(stack)和堆(heap)是非常重要的两个概念,网上讲解的资料也比较多,大家也应该知道栈和堆分别的用途是什么,接下来说一下我对栈与堆的本质区别的理解。
每个程序在运行时系统都会分配一块叫做栈的连续的内存区域,大小一般为1M或2M,是编译程序时指定的常数。堆则是系统中所有空闲的内存区域,因此是不连续的,而且有可能是虚拟内存。函数的调用和返回是通过栈来实现的,不细说了。程序中用到的变量(对象)所占据的内存空间既可以从栈上分配,也可以从堆中分配,区别如下:
1. 内存指针的性质不同
栈上面只能分配长度较小、大小固定的数据,变量的内存地址相对于栈指针的偏移量在编译时就可以确定,因而可以生成直接的内存访问的指令。
堆上面分配的空间大小在运行时指定,并且返回的内存地址只能在运行时得知,因此栈上需要有一个相应的变量来保存堆上分配的内存地址,地址的长度为32位或64位。于是访问堆上的内存单元只能通过间接访问的指令来操作。
2. 内存空间的回收方式不同
栈上面分配的内存在压栈和出栈的过程中自动分配自动回收,速度很快。
堆上面分配的内存则需要显式地释放掉或者做垃圾回收的处理,有可能会漏掉释放,造成所谓的内存泄漏。
理解栈和堆的概念对于编写正确的高效的程序非常重要,但是程序语言的语法对栈和堆的体现很隐晦。C++创建一个类的实例时,既可以指定在栈上分配,也可以指定在堆上分配。C#中值类型的变量在栈上分配,引用类型的变量在堆上分配,值类型的变量也可以通过装箱的方式在堆上分配,在unsafe代码中可以在栈上分配大小固定的字节数组,总的来说没有C++灵活。
最后做一个练习,思考一下下面的程序在运行中内存是怎样分配的:
public struct A
{
int field;
List<int> list = new List<int>();
}
public class B
{
int field;
List<A> list = new List<A>();
}
public class P
{
static void main(string[] args)
{
A a = new A();
B b = new B();
Console.Write("{0} and {1}", a, b);
}
}
{
int field;
List<int> list = new List<int>();
}
public class B
{
int field;
List<A> list = new List<A>();
}
public class P
{
static void main(string[] args)
{
A a = new A();
B b = new B();
Console.Write("{0} and {1}", a, b);
}
}