到目前为止,你写过的每个数组,大小都是在编译时就定死的:int scores[10]; 会预留十个位置,不管你需要的是三个还是三百个。只要大小在程序运行时才能知道,比如一个文件有多少行、用户会输入多少条记录,这种写法就行不通了。动态内存解决了这个问题:在需要的时候申请需要的字节数,用完后再还回去。
相关的工具都在 <stdlib.h> 中:
#include <stdlib.h>
为什么需要堆
C 语言给程序提供了两块主要的内存区域。局部变量位于自动存储区(栈):它们的大小由编译器决定,所在函数一返回就会消失。动态分配的内存位于堆:大小由你在运行时决定,这块内存会一直有效,直到你显式释放它。
第二个特性才是使用 malloc 的真正原因。函数可以分配一块内存,返回指针,而这块内存在调用者那里依然有效,局部数组永远做不到这一点。两块区域的详细对比见栈与堆。
malloc:申请字节
malloc 接收一个字节数,返回一个 void *,指向这么多字节的未初始化内存;如果请求失败,就返回 NULL。
这段短小的程序里有四个要点,它们就是全部的规矩:
- 大小表达式是
n * sizeof *scores。 把它读作“n 个scores所指向的东西”。 - 在任何使用之前,先检查结果是否为
NULL。 - 这块内存的用法和数组完全一样:
scores[i]之所以可行,是因为下标运算本质上就是指针运算。 - 只释放一次,然后把指针设为
NULL。
注意这里没有对返回值做强制类型转换。在 C 语言中,void * 会自动转换为任何对象指针类型,写成 (int *)malloc(...) 只会增加干扰,还会掩盖遗漏 <stdlib.h> 头文件的问题。(C++ 要求这个转换,C 不需要。)
sizeof 惯用写法
为什么用 sizeof *scores 而不是 sizeof(int)?因为它永远不会过时。
int *a = malloc(n * sizeof *a); /* n ints, whatever int is here */
long *b = malloc(n * sizeof *b); /* same line shape, right size */
/* the fragile form */
long *c = malloc(n * sizeof(int)); /* compiles, allocates too little */
最后一行是一个真实存在的 bug,而且没有任何编译器会警告:c 是 long *,分配的大小却是按 int 计算的,后半部分的每次写入都会越界。用 sizeof *c,大小就会自动跟随声明。
sizeof *p 并不会解引用 p:sizeof 在编译时只根据类型求值,所以即使 p 未初始化或为 NULL 也是安全的。
未初始化的内存
malloc 不会清空它给你的内存。这些字节里保存的是之前留下的任意内容。
如果你希望内存自动清零,calloc 可以一步完成,参见 calloc 和 realloc。
free:归还内存
free(p) 把这块内存还给分配器。三条规则:
- 传入
malloc返回的原始指针,而不是移动过的指针。free(p + 1)是未定义行为。 - 每块内存只释放一次。释放两次就是重复释放(double free),会破坏分配器自己的记录。
- 释放之后,指针就失效了。继续使用它就是释放后使用(use-after-free)。
int *p = malloc(sizeof *p);
free(p);
*p = 5; /* use-after-free - undefined behavior */
free(p); /* double free - undefined behavior */
这两行都不一定会立即崩溃,这正是它们危险的地方:程序可能运行好几分钟,然后在毫不相关的地方出错。最省事的防御办法是把指针清空:
free(p);
p = NULL;
C 标准明确规定 free(NULL) 什么也不做,所以之后再调用一次 free(p) 也无害,而 *p 会在真正出错的位置立即崩溃,而不是悄悄破坏数据。
一个完整的动态数组
这就是真实分配的样子:在运行时读取数量,按它确定内存大小,使用,然后释放。
当大小变大时,把数量转换为 size_t 就很重要了:如果 n 是普通的 int,n * sizeof *values 可能在乘积传给 malloc 之前就溢出,得到一块比预期小得多的内存。用 size_t 做乘法可以避免这个问题。
在函数内部分配
正是堆的生命周期规则让下面的写法合法,同时也让返回局部数组变得非法:
len + 1 为空终止符留出了位置,这和 字符串 中各处的大小规则相同。再注意函数上方的注释:当一个函数返回已分配的内存时,由谁来释放它是函数约定的一部分。把这一点写下来不是走形式,它是防止内存泄漏的唯一保障。
堆上的结构体
同样的惯用写法适用于任何类型:
注意第二个失败分支在返回之前释放了 p。一旦函数持有不止一块已分配的内存,每条错误路径都必须释放已经申请到的那些,这是内存泄漏中描述的泄漏最常见的来源。
检查清单
- 第一次使用之前,始终检查
malloc是否返回NULL。 - 用
n * sizeof *p计算大小,并以size_t做乘法。 - 在 C 语言中不要对返回值做强制类型转换。
malloc分配的内存未初始化;先写再读。- 用原始指针只释放一次,然后把它设为
NULL。 - 分配之后的每条错误路径,都必须释放已经持有的内存。
常见问题
C 语言中 malloc 是做什么的?
malloc(n) 向堆申请 n 字节的内存,并返回指向这块内存起始位置的指针;如果请求无法满足,就返回 NULL。这块内存会一直存在,直到你把同一个指针传给 free。和局部变量不同,函数返回时它不会被释放。
C 语言中 malloc 和 free 怎么用?
用 int *p = malloc(n * sizeof *p); 分配内存,在使用之前先检查 if (p == NULL),然后像数组一样使用它,用完后调用 free(p);,并且只调用一次。之后再写上 p = NULL,这样以后不小心再用到它时,会直接崩溃,而不是悄悄破坏数据。
为什么 malloc(n * sizeof *p) 比 malloc(n * sizeof(int)) 更好?
因为 sizeof *p 会自动跟随指针的类型。如果 p 后来改成了 long * 或 struct Point *,分配的大小会自动更新;而直接写 sizeof(int),大小就会变得不对,编译器也不会提示。
C 语言中不释放内存会怎样?
这块内存会在整个进程的生命周期内一直被占用,这就是内存泄漏。短小的程序可以侥幸没事,因为操作系统在程序退出时会回收所有内存;但长时间运行的程序每执行一轮就泄漏一点,最终会耗尽内存。参见 内存泄漏。