堆和栈的内存分配
对于现在你们这些年轻人来说,编程简直太容易了。如果你玩玩Ruby或者Python的话,只要创建对象或变量就好了,不用管它们存放在哪里。你并不关心它们是否存放在栈上或堆上。你的编程语言甚至完全不会把变量放在栈上,它们都在堆上,并且你也不知道是否是这样。
然而C完全不一样,因为它使用了CPU真实的机制来完成工作,这涉及到RAM中的一块叫做栈的区域,以及另外一块叫做堆的区域。它们的差异取决于取得储存空间的位置。
堆更容易解释,因为它就是你电脑中的剩余内存,你可以通过malloc访问它来获取更多内存,OS会使用内部函数为你注册一块内存区域,并且返回指向它的指针。当你使用完这片区域时,你应该使用free把它交还给OS,使之能被其它程序复用。如果你不这样做就会导致程序“泄露”内存,但是Valgrind会帮你监测这些内存泄露。
栈是一个特殊的内存区域,它储存了每个函数的创建的临时变量,它们对于该函数为局部变量。它的工作机制是,函数的每个函数都会“压入”栈中,并且可在函数内部使用。它是一个真正的栈数据结构,所以是后进先出的。这对于main中所有类似char section和int id的局部变量也是相同的。使用栈的优点是,当函数退出时C编译器会从栈中“弹出”所有变量来清理。这非常简单,也防止了栈上变量的内存泄露。
理清内存的最简单的方式是遵守这条原则:如果你的变量并不是从malloc中获取的,也不是从一个从malloc获取的函数中获取的,那么它在栈上。
下面是三个值得关注的关于栈和堆的主要问题:
如果你从malloc获取了一块内存,并且把指针放在了栈上,那么当函数退出时,指针会被弹出而丢失。
如果你在栈上存放了大量数据(比如大结构体和数组),那么会产生“栈溢出”并且程序会中止。这种情况下应该通过malloc放在堆上。
如果你获取了指向栈上变量的指针,并且将它用于传参或从函数返回,接收它的函数会产生“段错误”。因为实际的数据被弹出而消失,指针也会指向被释放的内存。
这就是我在程序中使用Database_open来分配内存或退出的原因,相应的Database_close用于释放内存。如果你创建了一个“创建”函数,它创建了一些东西,那么一个“销毁”函数可以安全地清理这些东西。这样会更容易理清内存。
最后,当一个程序退出时,OS会为你清理所有的资源,但是有时不会立即执行。一个惯用法(也是本次练习中用到的)是立即终止并且让OS清理错误。