Tan's Blog.

CSAPP动态内存分配和回收

字数统计: 1.1k阅读时长: 3 min
2019/05/26 Share

动态内存分配

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器分为两种类型:
显式分配器: 应用显式地释放任何已分配的块。
隐式分配器: 分配器检测一个已分配块何时不再被程序使用,那就释放这个块。隐式分配器也叫垃圾收集器,自动释放未使用的已分配块的过程称为垃圾收集.

malloc和free

#include <stdlib.h>
void *malloc(size_t size);
void free(void *ptr);

malloc: 分配至少size字节的存储器块,这个块会为可能包含在这个块内的任何数据对象类型做对齐。在Unix系统上,malloc返回一个8字节(双字)边界对齐的块。

分配器的数据结构

分配器需要一些数据结构,允许它来区别块边界,以及区别已分配块和空闲块。
1.隐式空闲链表
一个简单的堆块的格式是,块由一个字的头部、有效载荷,以及可能的额外填充组成。头部编码了这个块的大小,以及这个块是已分配的还是空闲的。
我们可以将堆组织为一个连续的已分配块和空闲块的序列,这种结构称为隐式空闲链表。分配器可以通过遍历堆中所有的块,从而间接遍历整个空闲块的集合。
隐式空闲链表的优点是简单。显著缺点是任何操作的开销,例如放置分配的块,要求空闲链表的搜索与堆中已分配块和空闲块的总数呈线性关系。
放置已分配的块的放置策略:
首次适配
下一次适配
最佳适配
2.显式空闲链表
使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。

垃圾回收

垃圾收集器定期识别垃圾块(即程序不再需要的已分配块),并相应的调用free,将这些块放回到空闲链表中.

Mark&Sweep(标记&清除)算法

由标记(mark)阶段和清除(sweep)阶段组成,标记阶段标记出根节点的所有可达的已分配的后继,而后面的清除阶段释放每个未被标记的已分配块。在标记阶段的末尾,任何未标记的已分配块都被认定是不可达的,是垃圾,可以在清除阶段回收。

C中常见的与内存相关的错误

存储器的错误总是令人沮丧的,特别是在运行了一段时间之后才显示出来,就特别特别的烦人了,列举一些常见的错误,仅供参考:

间接引用坏指针

将本来的地址引用写成了内容scanf(“%d”, &val)写成scanf(“%d”, val)

读未初始化的存储器:

在堆中申请了一块空间:int *y = (int *)Malloc(n * sizeof(int));由于堆中的空间是未被初始化的,下面的使用就会出错:y[i] += A[i][j] * x[j];推荐使用calloc函数

允许栈缓冲溢出:

推荐使用fgets函数

假设指针和它指向的对象是相同大小的

使用int **A = (int **)Malloc(n * sizeof(int));本来是想创建的一个int *的数组,但是sizeof上面用到的确是int

错位

申请了n个空间,却要访问n+1处位置

引用指针而不是它指向的对象

*size–; /* This should be (*size)– */
其中,–和*有相同的优先级,由于这是右结合。所以先–再*,就出错了。

误解指针运算

p += sizeof(int); /* Should be p++ */
指针p++就会指向下一个位置,+= int的大小的话,就跳了几个数据了

引用不存在的变量

本地变量在栈中创建,函数结束以后就已经不存在了。

引用已经释放了的块中的数据

在行10的时候已经将块释放了,在行14的时候又在使用.

CATALOG
  1. 1. 动态内存分配
    1. 1.1. malloc和free
    2. 1.2. 分配器的数据结构
  2. 2. 垃圾回收
    1. 2.1. Mark&Sweep(标记&清除)算法
  3. 3. C中常见的与内存相关的错误
    1. 3.1. 间接引用坏指针
    2. 3.2. 读未初始化的存储器:
    3. 3.3. 允许栈缓冲溢出:
    4. 3.4. 假设指针和它指向的对象是相同大小的
    5. 3.5. 错位
    6. 3.6. 引用指针而不是它指向的对象
    7. 3.7. 误解指针运算
    8. 3.8. 引用不存在的变量
    9. 3.9. 引用已经释放了的块中的数据