很多时候,你会被问到,也许是在面试的时候,堆和栈有什么区别?什么时候变量被分配到堆中?什么时候被分配到栈中?不是提问者故意刁难,也不是这样问,就显得自己有多么高大上。作为一个码农,不知道答案也许没关系,但这样一来,你也就未必真的了解自己的代码。
堆和栈,都是机器的一块内存区域。区别是,两者的用途和访问速度都不一样。栈直接参与系统调用,作为存放临时数据的地方,它的地址是放在专门的寄存器中的。这些寄存器,就是CPU直接访问的地方。这样一来,栈存取速度特别快,但能支持的数据类型,就非常有限,仅限于整数,指针,浮点数等系统直接支持的数据类型。涉及到函数调用的时候,系统先把函数返回地址入栈,然后依次放入函数局部变量。执行完后依次出栈,最后是函数返回地址。这种方式,直接决定了函数局部变量的生命周期。
相对于栈来说,堆要大得多。它是C++编译器定义的内存区域,通过编译器来访问。这块区域,就是给程序动态分配内存用的。当编译器看到new关键字调用的时候,它会去堆中尝试分配指定大小的内存区域,如果成功,就返回内存块的首地址;如果失败,则返回NULL。这块内存在程序运行时是不会被系统自动释放的,需显式调用delete来释放。
另外,C++编译器还定义全局区以存放全局或者静态变量。它还定义常量区以存放常量。
了解这些,你就会明白,把栈变量的地址返回给指针没有任何意义,而程序的输出结果也会出乎你的意料。当你使用new关键字创建对象的时候,返回值必定是个地址。过多使用new分配内存,会导致内存碎片和程序效率降低。在函数体体定义int i=0,i会放到栈中;而在函数体外定义,它会被放到全局区(注:类定义中初始化变量是不允许的);如果是在函数内部定义int *i = new int,i又会被放到堆中,这个时候,i代表的内存块是不是被清除的,尽管在退出函数的情况下。
下面是一个例子,用来说明C++针对不同变量的内存分配情况。
#include <iostream>
using namespace std;
int g;
class myclass{
int i;
double d;
long l;
int arri[2];
public:
void f(string className){
int a = 0;
cout << "address of function variable from " << className <<".f a:" << (long)&a << endl;
}
};
int main(){
myclass b,c;
cout << "size of b:" <<sizeof(b) << endl;
cout << "address of b:" << (long)&b << endl;
cout << "size of c:" <<sizeof(c) << endl;
cout << "address of c:" << (long)&c << endl;
myclass *d = new myclass;
cout << "size of d:" <<sizeof(*d) << endl;
cout << "address of d:" << (long)d << endl;
b.f("b");
c.f("c");
cout << "address of global variable g:" << (long)&g << endl;
}
执行后输出结果是:
这里,类变量b,c以及函数f里的int变量a,都是局部变量,放在栈中。其中,b和c的地址是连续的,正好相差32个字节。类变量d通过new关键字分配内存,所以它的起始地址就跟b和c不一样(我的环境里是25769945744),虽然也是局部变量,但是是放在堆中的。这里注意,两个局部变量a的地址是完全一样,虽然是通过不同的函数调用来定义。这也正好体现了栈的特点:栈自动分配和释放内存;栈内存可迅速重用。