注意:这个答案方式太长了。有时间我会删减的。同时,如果您能想到有用的修改,请发表评论。
要回答您的问题,我们首先需要定义两个内存区域,称为stack和heap。
堆栈
将堆栈想象成一堆盒子。每个框代表一个函数的执行。一开始,当调用main 时,地板上有一个盒子。您定义的任何局部变量都在该框中。
一个简单的例子
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return a + b;
}
在这种情况下,地板上有一个盒子,其中包含变量 argc(整数)、argv(指向 char 数组的指针)、a(整数)和 b(一个整数)。
不止一盒
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
int c = a + b;
c++;
return c;
}
现在,地板上有一个盒子(用于main),上面有argc、argv、a 和b。在该框的顶部,您还有另一个框(用于do_stuff),带有a、b 和c。
这个例子说明了两个有趣的效果。
您可能知道,a 和 b 是按值传递的。这就是为什么do_stuff 的框中有这些变量的副本。
请注意,对于这些变量,您不必为 free 或 delete 或任何东西。当您的函数返回时,该函数的框被销毁。
框溢出
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
return do_stuff(a, b);
}
这里,地板上有一个盒子(main,和以前一样)。然后,您有一个带有a 和b 的框(用于do_stuff)。然后,你有另一个盒子(do_stuff 调用自己),同样是 a 和 b。然后另一个。很快,你就有了一个堆栈溢出。
堆栈摘要
将堆栈想象成一堆盒子。每个框代表一个正在执行的函数,并且该框包含该函数中定义的局部变量。当函数返回时,那个盒子就被销毁了。
更多技术资料
- 每个“盒子”被正式称为一个堆栈帧。
- 是否注意到您的变量是如何具有“随机”默认值的?当一个旧的堆栈帧被“销毁”时,它就不再相关了。它不会被归零或类似的东西。下次堆栈帧使用该部分内存时,您会在局部变量中看到旧堆栈帧的位。
堆
这就是动态内存分配发挥作用的地方。
将堆想象成一片无尽的绿色记忆草地。当您调用malloc 或new 时,会在堆中分配一块内存。给你一个指针来访问这块内存。
int main(int argc, char * argv[])
{
int * a = new int;
return *a;
}
这里,在堆上分配了一个新整数的内存。您会得到一个名为 a 的指针,它指向该内存。
-
a 是一个局部变量,所以它在main 的“盒子”中
动态内存分配原理
当然,使用动态分配的内存似乎会到处浪费一些字节用于指针。但是,如果没有动态内存分配,有些事情是无法(轻松)做到的。
返回一个数组
int main(int argc, char * argv[])
{
int * intarray = create_array();
return intarray[0];
}
int * create_array()
{
int intarray[5];
intarray[0] = 0;
return intarray;
}
这里发生了什么?您在create_array 中“返回一个数组”。实际上,您返回一个指针,它只指向包含数组的create_array“框”部分。当create_array 返回时会发生什么?它的盒子被破坏了,你可以预料到你的数组随时会损坏。
改为使用动态分配的内存。
int main(int argc, char * argv[])
{
int * intarray = create_array();
int return_value = intarray[0];
delete[] intarray;
return return_value;
}
int * create_array()
{
int * intarray = new int[5];
intarray[0] = 0;
return intarray;
}
因为函数返回不会修改堆,所以您宝贵的intarray 毫发无损。完成后记得delete[]它。