堆内存

堆内存的分配和释放


调用栈是由编译器自动生成的代码来管理的, 程序员对栈帧的出栈入栈没有直接的控制.
如果想要对内存有更多的控制, 需要时分配, 不再需要时释放, 可以使用堆内存.
堆内存和栈内存地址相距甚远.

  1. int * arr;
  2. arr = malloc(6 * sizeof(int));
  3. arr[0] = 11;
  4. arr[2] = 33;
  5. free(arr);

第 1 行, 声明一个指针, 这个指针就存在于调用栈某个位置.

内存占用情况:

标志符地址
arr100?

第 2 行, 调用 malloc, 返回一个有效的堆地址, 指向一片连续的, 足够存储 6 个 int 型数值的内存区.
将该地址作为 arr 的值存储, 分配得到的内存未被初始化.
malloc 有可能失败, 系统可能无法提供所需的内存, 如果分配内存失败会返回 NULL, 程序应该处理好这个问题.

  1. if ( (arr = malloc(6 * sizeof(int))) == NULL ) exit(EXIT_FAILURE);

内存占用情况 ( int 占用 4 个字节):

标志符地址地址
arr1001000010020?
 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 会导致内存泄露, 这是个严重的问题.

堆内存地址作为函数的返回值


  1. int * f1(int n)
  2. {
  3. int * p;
  4. p = malloc(n * sizeof(int));
  5. return p;
  6. }
  7.  
  8. void f2(void)
  9. {
  10. int * arr;
  11. arr = f1(6);
  12. /* 返回位置 */
  13. arr[5] = 32;
  14. free(arr);
  15. }

f1 返回 p 之前, 内存占用情况:

标志符地址地址
p1031000010020?
值地址10210010016?
返回位置101第 12 行10012?
arr100?10008?
 10004?
10000?
(a) 栈内存(b) 堆内存

f1 返回 之后, 内存占用情况:

标志符地址地址
arr1001000010020? => 32
 10016?
10012?
10008?
10004?
10000?
(a) 栈内存(b) 堆内存

f1 返回之后, 尚未调用 free, 被分配的堆内存未被释放, 那就仍然是可用的.
arr[5] = 32, 这句可以顺利执行.

这里, malloc 和 free 分别在两个函数中被调用, 有时这是必要的, 也是容易出错的.
这样做, 追踪下面的问题会比较麻烦:

  • 通过调用 malloc 分配的内存, 随后是否通过调用 free 进行了释放
  • 通过调用 free 释放的内存, 是不是由之前调用 malloc 分配得到的

《 C 语言程序设计进阶教程》