7.2. C语言分配函数简析

深入探讨C++的内存分配机制之前,先简要了解C语言内存分配函数中最具代表性的两位成员:malloc()和free()。除此之外还存在诸多相关函数,如calloc()、realloc()和aligned_alloc(),更不用说那些为特殊场景服务的操作系统级内存管理接口,但上述两个函数已足够支撑这里的讨论。

作为一本关于C++内存管理的书籍,我将使用这些函数的C++版本(来自<cstdlib>而非<stdlib.h>)。这实际上不会影响将要编写的代码,唯一的区别在于C++中这些函数位于std命名空间。

这两个函数的声明如下:

void* malloc(size_t n);
void free(void *p);

malloc(n) 的作用是查找一个具有至少 n 个连续可用字节的位置,并可能将该位置标记为“已占用”,然后返回一个指向该内存块起始位置的抽象指针(void*)。返回的指针必须对齐到给定机器中最不利的自然对齐情况,所以必须满足 std::max_align_t 的对齐要求。在大多数机器上,该类型是 double 的别名。

有趣的是,malloc(0) 也是合法的,但这种调用的结果是由具体实现定义的:调用 malloc(0) 可能返回 nullptr,也可能返回一个非空指针。无论 malloc(0) 返回的指针是否为空,都不应对其进行解引用。

如果 malloc() 无法分配内存,会返回 nullptr,C 语言不像 C++ 那样支持异常机制。在现代 C 语言中(自 C11 起),malloc() 的实现必须线程安全,并且在并发调用时,必须适当地与其他 C 分配函数同步,包括与 free() 的同步。

free(p) 的作用是确保 p 所指向的内存变为可用状态,以供后续的分配请求使用,前提是 p 指向的是一个通过 malloc() 等内存分配函数分配且尚未释放的内存块。试图释放一个未通过此类分配函数分配的地址是未定义行为(UB),请不要这样做!另外,当内存释放后,就不再视为已分配,所以如下代码会导致未定义行为(UB):

#include <cstdlib>

int main() {
  using std::malloc, std::free;
  int *p = static_cast<int*>(malloc(sizeof(int)));

  free(p); // 可以,因为 p 是通过 malloc() 分配的
  free(p); // 绝对不行!除非(运气好?)p 已经是空指针了
}

free(nullptr) 不执行任何操作,并且在撰写本文时,几十年来都定义为不执行任何操作。如果代码库中存在在调用 free() 前检查 p != nullptr 的代码 —— 例如,if(p) free(p) —— 可以安全地移除这个检查。

有时(但并非总是)会使用这些 C 函数来实现自定义的 C++ 分配函数。这些函数是有效的,已广泛使用,并且确为底层的抽象,我们可以利用它们来构建更高级的抽象。