二级指针释放必须逆向逐层操作:先逐行 delete[] arr[i] 或 free(arr[i]),再 delete[] arr 或 free(arr),否则导致内存泄漏、段错误;nullptr 安全可跳过,但不可解引用。

二级指针内存释放的典型错误:只 free 一级指针
直接对 free(p) 或 delete[] p 释放二级指针本身,而不遍历释放它指向的每一级内存,会导致严重的内存泄漏——因为 p 只是存了一堆地址的指针数组,真正分配的内存块在更深层。
常见错误现象:valgrind 报告“definitely lost”大量字节;程序运行越久内存占用越高;delete[] p 后再访问 p[0] 触发段错误(use-after-free)。
- 二级指针如
int** arr,通常对应“数组的数组”,即先new int*[rows],再对每个arr[i] = new int[cols] - 释放必须逆向操作:先逐行
delete[] arr[i],再delete[] arr - 若某行未成功分配(比如中途
new失败返回nullptr),对应位置为nullptr,delete[] nullptr是安全的,但不能跳过检查直接解引用
malloc + free 方式下如何安全释放
用 malloc 分配的二级指针,必须严格用 free 逐层释放,且顺序和分配完全相反。C 风格分配常用于兼容 C 接口或手动控制内存布局。
示例场景:构建一个动态大小的字符串二维数组 char** lines,每行长度不固定:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
// 分配
char** lines = (char**)malloc(n * sizeof(char*));
for (int i = 0; i < n; i++) {
lines[i] = (char*)malloc(strlen(src[i]) + 1);
strcpy(lines[i], src[i]);
}
<p>// 释放 —— 必须先子后父
for (int i = 0; i < n; i++) {
if (lines[i]) free(lines[i]); // 防空指针,虽非必须但推荐
}
free(lines); // 最后才释放指针数组本身- 不能写成
free(lines[i])在外层循环之后再free(lines)—— 这没问题,但顺序不能颠倒 -
free(nullptr)是合法的,但某些老 libc 版本可能不支持,显式判空更稳妥 - 如果某次
malloc失败,应立即释放已分配的部分并返回错误,否则造成泄漏
new/delete[] 混用会崩溃
用 new[] 分配的内存,必须用 delete[] 释放;用 new(无方括号)分配的单个对象,必须用 delete。混用(比如 new[] + delete)是未定义行为,多数情况下程序当场崩溃或静默损坏堆管理结构。
-
int** p = new int*[10]→ 必须delete[] p,不是delete p -
p[i] = new int[5]→ 必须delete[] p[i],不是delete p[i] - 编译器不会报错,但运行时堆校验(如 GCC 的
-fsanitize=address)会捕获这类错误 - 现代 C++ 更推荐用
std::vector<:vector>></:vector>替代裸二级指针,自动管理生命周期
释放后置空指针能避免二次释放吗
置空(如 lines[i] = nullptr)本身不能防止二次释放,但它让后续误调用 free(nullptr) 变得安全,并帮助调试时快速识别是否已释放。
- 释放后置空是良好习惯,尤其在多路径退出或异常路径中
- 但要注意:如果存在多个别名指针(如
char** q = lines),只置空lines不影响q,仍可能误用q - 真正防二次释放靠逻辑控制(如 RAII、智能指针)或运行时标记(如自定义内存池),裸指针层面只能靠人工约定
- 对于局部二级指针,函数结束即销毁,置空意义不大;对类成员或全局变量,建议释放后统一置空
二级指针释放真正麻烦的不是语法,而是分配路径是否完整、异常是否打断释放流程、以及是否有多个持有者同时认为自己负责释放。手动管理时,每一分配点都必须有且仅有一个明确的释放点,漏掉任意一层都会导致泄漏或崩溃。

















