第 19 讲:内存分配
前文我们已经说了很多有关 C 语言的语法了。今天我们来讲一点不同的理论:堆内存(Heap Memory)和栈内存(Stack Memory)。
堆内存和栈内存的定义
堆内存和栈内存是两个不同的存储空间,由于名称不同,所以它们各自的功能也不同。堆内存专门用来管理我们程序使用的大量数据信息,当程序员不用的时候,可以自己手动释放掉(具体我们稍后再说)。不过前文我们都没有用过堆内存。
栈内存指的是,当一个函数在生成的时候,就会在栈内存上分配指定的函数执行信息,以及参数、变量信息。当函数执行完毕后,这些信息会跟着函数销毁而自动销毁。这些内容是程序为我们直接完成的,无需我们手动去搞。举个例子。
当我们调用 f
函数时,系统就会为我们自动生成一个函数栈,分配其中需要用到的 x
、z
变量的空间以可以存放它们,然后生成足够的空间可以让我们去执行这些内容。
当执行完毕整个函数(调用返回语句 return z;
)后,f
函数被销毁,此时,f
函数栈使用的空间就会被立刻释放掉,留给其它程序或下一次执行某处的时候用,这样就可以节约内容使用。不然一直占用着,程序的使用内存就会越来越大。
当 f
执行完后,里面的变量的内存就会一起被释放,因为这些变量所处的内存的地方,就在这个函数栈里。这就是栈内存的方式。C 语言确实也默认所有在函数内定义的默认类型变量都是这样的生命周期。这些自动化处理的变量也就是前文提到过的 auto
类型变量。比如上文实例给出的 auto int z = 3
就是一个自动化处理的变量,而且一般我们都不写这个 auto
关键字。
使用堆内存
既然栈内存这么不方便,那么可以自定义使用堆内存吗?是的,这也是我们接下来要提到的话题。C 语言为我们贴心地搞了一个堆内存的体系,让我们可以更加灵活处理很多复杂的内存分配的机制,也可以同时去避免函数栈销毁后同时销毁变量本身的方式。
下面我们将开始尝试使用堆内存分配内存空间,并手动释放它们。不过我们需要介绍几个常用函数,它们在使用的时候都需要在前文 #include
指令处添加 #include <malloc.h>
头文件导入指令。
malloc
函数
考虑如下代码。
我们尝试在堆内存里分配了一个足够存放 int
的空间,而这坨空间的地址就是 malloc
函数的返回值。请注意,malloc
函数是具有通用性的,所以它可以为任何其它类型进行分配内存空间,所以此时我们需要把得到的地址强制转换为 int *
类型后赋值给 p
,于是 p
就指向了这块内存了。
然后我们尝试去比较 p
。如果 p
不为 NULL
则继续往下执行。这是因为 malloc
函数在分配内存失败时将会返回 NULL
值。当它不为 NULL
,就说明了变量被成功分配内存。另外,由于 NULL
和 (void *)0
等价,所以它实际上也是 0 的特殊写法。所以 p != NULL
依然可以认为是在和 0 进行比较,所以可以省略 != 0
部分,即改写为 p
即可:
free
函数
这个函数用于释放掉用完的内存。上文里如果我们已经完成了使用 p
的操作,那么我们就可以直接释放掉它了,所以我们把这个指针变量写到 free
函数里当参数就可以完成释放了。
realloc
函数
可以从单词上看出,realloc
就是 re-alloc。re- 前缀表示“反复”,“又”、“再一次”的意思,而 alloc 是 allocation,即分配的简写。所以这个函数的用法是将已经分配过的内存空间再一次重新分配一下,这一次可以更改内存分配的大小。如果你对这块内存目前的大小不满意,你可以调大这个内存空间,当然你也可以调小。它的写法是这样的:
写法很简单,第一个参数传入需要扩容和缩容的内存块指针,第二个参数传入的则是新的分配大小。在内存里,realloc
会为我们自动往后扩容和缩容空间。当扩容的时候,如果发现后续连续空间不能使用的时候,它就会自动把数据拷贝到可以连续分配到足够内存空间的某处上,然后把这些刚才拷贝后的备份放到相对对应好的位置上,并返回新内存块的首地址。这里是 pn
得到的内存块重新分配后,再次赋值给 pn
。
请注意,这种赋值格式是存在严重的内存 bug 的:
C6308: 'realloc' might return null pointer: assigning null pointer to 'pn', which is passed as an argument to 'realloc', will cause the original memory block to be leaked.
这段话的意思是,如果你要使用 realloc
函数,就最好不要使用同一个变量来接受分配的地址。realloc
是可能返回 NULL
的,如果你用同一个变量接收的话,就可能导致新得到的地址是 NULL
,这样你原本的内存块就找不到了,此时出现内存泄漏的 bug。