一,堆管理器
在程序运行的过程中,堆可以提供动态分配的内存,从而允许程序申请大小未知的内左 堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长,这和栈是相反的(栈是由高地址向低地址方向生长)。我们一般称管理堆的程序为堆管理器。
每个系统都有自己的堆管理器,堆利用就是针对堆管理器的一种利用思路。这是一种“中介”的思想, 比如现实中,你要购买一套房子,你可以告诉中介你对房子的需求,如面积、地段、学区等,中介给你推荐适合的房子;当你想卖掉房子时,可以告诉中介房子的基本信息、出售价格等, 中介帮你寻找买家。堆也是这样,为了避免系统与用户频繁交互(因为和操作系统交互非常耗时),就有了堆管理器。实际上,用户平时申请(malloc)和释放free) 堆块的时候,是和堆管理器直接接触的。
不同平台的堆管理器是不一样的,比如 Windows、 Linux、 Mac的处理机制不一样,用请求堆块和释放堆块的流程也不一样。在本书中,我们主要研究 Linux平台上的 ptmalloc 机制。以及在ptmalloc 机制上的利用。
二,ptmalloc堆管理器的基本功能
堆管理器处于用户和内核之间,主要负责完成用户的两个需求:malloc(申请堆块) 和 (释放堆块)。
一般来说,用户不管是请求堆块还是释放堆块,都不是直接和操作系统打交道,而是和堆管理器打交道。当用户请求一个堆块时,堆管理器需要响应用户的申请内存请求,并且向操作系统申请内存,然后将这个堆块返回给用户程序。这种响应的逻辑对应每个堆管理器的算法,在ptmalloc堆管理器中,内核一般会预先分配一块很大的连续的内存,然后让堆管理器通过某种算法管理这块内存,只有当出现堆空间不足的情况时,ptmalloc堆管理器才会再次与操作系统进行交互,通过系统调用申请内存。释放堆块的时候也是一样,ptmalloc堆管理器需要管理用户释放的堆块。在 ptmalloc中,会通过一系列的 bin进行管理。值得一提的是,用户请求的堆块不仅来自系统分配的堆块,也可能来自这些堆管理器管理的释放之后的堆块。
□chunk: 在ptmalloc中, 堆管理器会利用malloc chunk结构体管理所有的堆块,所以我们将堆管理器中出现的堆块叫作 chunk,而将在程序中直接使用的内存块称为堆(heap)。
□free:也叫作释放,即在程序中调用 free 函数释放这个堆块。
□bin: 在ptmalloc中用来保存没有使用的 chunk(释放之后的 chunk)。根据需求的不同,bin在数据结构上表现为单向链表或者双向循环链表。
三,malloc和free
malloc是用户程序申请堆块的常用函数
我们可以了解 malloc函数的一些基本信息:
malloc
(memory allocate)函数用于在堆上分配指定大小的内存块。它返回一个指向新分配内存的指针,如果分配失败则返回 NULL
。
void *malloc(size_t size); |
size
参数指定了要分配的字节数。
int *ptr = (int *)malloc(sizeof(int) * 10); | |
if (ptr == NULL) { | |
// 内存分配失败,处理错误 | |
} else { | |
// 使用分配的内存 | |
} |
在这个例子中,我们分配了一个能够存储10个整数的内存块,并将返回的指针转换为 int *
类型。如果没有空间可用, 会返回 null(0)。如果malloc 函数的参数是 0, 会返回一个最小的 chunk。如果malloc函数的参数是一个负数,那么会申请一个很大的空间。但一般来说,这么大的空间是不会申请成功的,所以会返回null(0)。
free是用户程序释放一个堆块时常用的函数
free
函数用于释放之前通过 malloc
、calloc
或 realloc
分配的内存块。释放内存后,相应的指针应被设置为 NULL
,以防止成为“悬垂指针”(dangling pointer)。
void free(void *ptr); |
ptr
参数是一个指向要释放的内存块的指针。
int *ptr = (int *)malloc(sizeof(int) * 10); | |
if (ptr != NULL) { | |
// 使用分配的内存 | |
// ... | |
// 释放内存 | |
free(ptr); | |
ptr = NULL; // 防止悬垂指针 | |
} |
注意事项
- 内存泄漏:如果分配的内存没有被释放(即忘记调用
free
),则会导致内存泄漏。这会导致程序占用的内存不断增加,最终可能导致系统资源耗尽。 - 悬垂指针:释放内存后,原来的指针仍然指向已经被释放的内存块。这种指针被称为悬垂指针。访问悬垂指针是危险的,因为它可能导致不可预测的行为,包括程序崩溃。因此,释放内存后,应将指针设置为
NULL
。 - 多次释放:尝试释放同一块内存多次(即多次调用
free
)也是错误的。这可能导致不可预测的行为,包括程序崩溃。 - 内存对齐和碎片化:频繁地分配和释放小块内存可能会导致内存碎片化,降低内存使用效率。此外,某些硬件平台对内存对齐有要求,如果分配的内存没有正确对齐,可能会导致性能下降或硬件异常。
因此,在使用 malloc
和 free
时,需要谨慎处理内存分配和释放,以确保程序的正确性和稳定性。