堆内存
堆内存的分配和释放
调用栈是由编译器自动生成的代码来管理的, 程序员对栈帧的出栈入栈没有直接的控制.
如果想要对内存有更多的控制, 需要时分配, 不再需要时释放, 可以使用堆内存.
堆内存和栈内存地址相距甚远.
- int * arr;
- arr = malloc(6 * sizeof(int));
- arr[0] = 11;
- arr[2] = 33;
- free(arr);
第 1 行, 声明一个指针, 这个指针就存在于调用栈某个位置.
内存占用情况:
标志符 | 地址 | 值 |
---|---|---|
arr | 100 | ? |
第 2 行, 调用 malloc, 返回一个有效的堆地址, 指向一片连续的, 足够存储 6 个 int 型数值的内存区.
将该地址作为 arr 的值存储, 分配得到的内存未被初始化.
malloc 有可能失败, 系统可能无法提供所需的内存, 如果分配内存失败会返回 NULL, 程序应该处理好这个问题.
- if ( (arr = malloc(6 * sizeof(int))) == NULL ) exit(EXIT_FAILURE);
内存占用情况 ( int 占用 4 个字节):
标志符 | 地址 | 值 | 地址 | 值 |
---|---|---|---|---|
arr | 100 | 10000 | 10020 | ? |
10016 | ? | |||
10012 | ? | |||
10008 | ? => 33 | |||
10004 | ? | |||
10000 | ? => 11 | |||
(a) 栈内存 | (b) 堆内存 |
第 3 行, arr[0] = 11, 导致堆内存 10000 处发生了变化.
第 4 行, arr[2] = 33, 导致堆内存 10008 处发生了变化.
这条语句实际上做的是:
- 将 arr 转化为地址, 这里是 10000
- 下标是 [2], 将 sizeof(int) 乘以 2, 加到 10000 上, 得到新地址 10008
- 将地址 10008 处的值改为 33
第 5 行, free(arr)
当不再需要时, 堆内存空间必须被释放, 可以通过调用 free 来实现.
每一次调用 free, 指定的内存区会被全部释放, 无法只释放其中一部分.
调用了 malloc 但忘记调用 free 会导致内存泄露, 这是个严重的问题.
堆内存地址作为函数的返回值
- int * f1(int n)
- {
- int * p;
- p = malloc(n * sizeof(int));
- return p;
- }
- void f2(void)
- {
- int * arr;
- arr = f1(6);
- /* 返回位置 */
- arr[5] = 32;
- free(arr);
- }
f1 返回 p 之前, 内存占用情况:
标志符 | 地址 | 值 | 地址 | 值 |
---|---|---|---|---|
p | 103 | 10000 | 10020 | ? |
值地址 | 102 | 100 | 10016 | ? |
返回位置 | 101 | 第 12 行 | 10012 | ? |
arr | 100 | ? | 10008 | ? |
10004 | ? | |||
10000 | ? | |||
(a) 栈内存 | (b) 堆内存 |
f1 返回 之后, 内存占用情况:
标志符 | 地址 | 值 | 地址 | 值 |
---|---|---|---|---|
arr | 100 | 10000 | 10020 | ? => 32 |
10016 | ? | |||
10012 | ? | |||
10008 | ? | |||
10004 | ? | |||
10000 | ? | |||
(a) 栈内存 | (b) 堆内存 |
f1 返回之后, 尚未调用 free, 被分配的堆内存未被释放, 那就仍然是可用的.
arr[5] = 32, 这句可以顺利执行.
这里, malloc 和 free 分别在两个函数中被调用, 有时这是必要的, 也是容易出错的.
这样做, 追踪下面的问题会比较麻烦:
- 通过调用 malloc 分配的内存, 随后是否通过调用 free 进行了释放
- 通过调用 free 释放的内存, 是不是由之前调用 malloc 分配得到的