栈(Stack)和堆(Heap)
前面说过,我们可以把内存简单粗暴地想象成一个巨大的字节数组。事实上,它被更加合理地划分成了两部分,即栈和堆。
有些人可能已经听说过一些关于堆和栈的神秘传说,例如“栈从上往下增长,而堆则是从下往上”,或是“栈的数量很多,但堆只有一个”云云。其实这些事情都是无关紧要的。在 C 语言中,处理好栈和堆确实是件麻烦的事情,但这并不代表它们很神秘。实际上,它们只是内存中的两块不同的区域,分别用来完成不同的任务而已。
栈(The Stack)
栈是程序赖以生存的地方,所有的临时变量和数据结构都保存于其中,供你读取及编辑。每次调用一个新的函数,就会有一块新的栈区压入,并在其中存放函数内的临时变量、传入的实参的拷贝以及其它的一些信息。当函数运行完毕,这块栈区就会被弹出并回收,供其他函数使用。
我喜欢把栈想象成一个建筑工地。每次需要干点新事情的时候,我们就圈出一块地方来,放工具、原料,并在这里工作。如果需要的话,我们也可以到工地的其他地方,甚至是离开工地。但是我们所有的工作都是在自己的地方完成的。一旦工作完成,我们就把工作成果转移到新的地方,并把现在工作的地方清理干净。
堆(The Heap)
堆占据另一部分内存,主要用来存放长生命周期期的数据。堆中的数据必须手动申请和释放。申请内存使用 malloc
函数。这个函数接受一个数字作为要申请的字节数,返回申请好的内存块的指针。
当使用完毕申请的内存,我们还需要将其释放,只要将 malloc
函数返回的指针传给 free
函数即可。
堆比栈的使用难度要大一些,因为它要求程序员手动调用 free
函数释放内存,而且还要正确调用。如果不释放,程序就有可能不断申请新的内存,而不释放旧的,导致内存越用越多。这也被称为内存泄漏。避免这种情况发生的一个简单有效的办法就是,针对每一个 malloc
函数调用,都有且只有一个 free
函数与之对应。这某种程度上就能保证程序能正确处理堆内存的使用。
我把堆想象成一个自助存储仓库,我们使用 malloc
函数申请存储空间。我们可以在自主存储仓库和建筑工地之间自由存取。它非常适合用来存放大件的偶尔才用一次的物件。唯一的问题就是在用完之后要记得使用 free
函数将空间归还。