C++ 内存管理详解:从内存分区、malloc/free 到 new/delete

🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 | 《C语言基础》 | 《数据结构》 | 《机器学习导论》 | 《前端基础》 | 《python基础》 ✨ 数据即知识,压缩即智能
目录
- [C++ 内存管理详解:从内存分区、malloc/free 到 new/delete](#C++ 内存管理详解:从内存分区、malloc/free 到 new/delete)
-
- [一、C/C++ 程序的内存区域划分](#一、C/C++ 程序的内存区域划分)
-
- [1.1 为什么要先理解内存分区?](#1.1 为什么要先理解内存分区?)
- [1.2 常见内存区域](#1.2 常见内存区域)
- 二、栈区:局部变量主要生活的地方
-
- [2.1 栈区存什么?](#2.1 栈区存什么?)
- [2.2 栈区的特点](#2.2 栈区的特点)
- 三、堆区:动态内存申请的地方
-
- [3.1 堆区存什么?](#3.1 堆区存什么?)
- [3.2 堆区的特点](#3.2 堆区的特点)
- 四、数据段和代码段
-
- [4.1 数据段 / 静态区](#4.1 数据段 / 静态区)
- [4.2 代码段 / 常量区](#4.2 代码段 / 常量区)
- 五、一道经典内存分布题
-
- [5.1 全局和静态变量](#5.1 全局和静态变量)
- [5.2 普通局部变量和数组](#5.2 普通局部变量和数组)
- [5.3 指针变量和字符串常量](#5.3 指针变量和字符串常量)
- [5.4 指针变量和堆空间](#5.4 指针变量和堆空间)
- [六、C 语言动态内存管理:malloc/calloc/realloc/free](#六、C 语言动态内存管理:malloc/calloc/realloc/free)
-
- [6.1 malloc](#6.1 malloc)
- [6.2 calloc](#6.2 calloc)
- [6.3 realloc](#6.3 realloc)
- [6.4 realloc 的安全写法](#6.4 realloc 的安全写法)
- [6.5 free](#6.5 free)
- [七、malloc、calloc、realloc 的区别](#七、malloc、calloc、realloc 的区别)
- [八、C++ 为什么还要引入 new/delete?](#八、C++ 为什么还要引入 new/delete?)
-
- [8.1 malloc/free 在 C++ 中还能用吗?](#8.1 malloc/free 在 C++ 中还能用吗?)
- [8.2 new/delete 的基本用法](#8.2 new/delete 的基本用法)
- [九、new/delete 操作自定义类型](#九、new/delete 操作自定义类型)
-
- [9.1 malloc/free 只管空间](#9.1 malloc/free 只管空间)
- [9.2 new/delete 会调用构造和析构](#9.2 new/delete 会调用构造和析构)
- [9.3 数组对象也一样](#9.3 数组对象也一样)
- [十、operator new 和 operator delete 是什么?](#十、operator new 和 operator delete 是什么?)
-
- [10.1 new 和 operator new 不是一回事](#10.1 new 和 operator new 不是一回事)
- [10.2 delete 和 operator delete 也不是一回事](#10.2 delete 和 operator delete 也不是一回事)
- [10.3 operator new 底层通常还是 malloc](#10.3 operator new 底层通常还是 malloc)
- [十一、new/delete 的底层原理](#十一、new/delete 的底层原理)
-
- [11.1 new 单个对象的过程](#11.1 new 单个对象的过程)
- [11.2 delete 单个对象的过程](#11.2 delete 单个对象的过程)
- [11.3 new\[\] 的过程](#11.3 new[] 的过程)
- [11.4 delete\[\] 的过程](#11.4 delete[] 的过程)
- [十二、定位 new:在已有空间上构造对象](#十二、定位 new:在已有空间上构造对象)
-
- [12.1 什么是定位 new?](#12.1 什么是定位 new?)
- [12.2 定位 new 的完整释放流程](#12.2 定位 new 的完整释放流程)
- [12.3 定位 new 用在哪里?](#12.3 定位 new 用在哪里?)
- [十三、malloc/free 和 new/delete 的区别](#十三、malloc/free 和 new/delete 的区别)
-
- [13.1 共同点](#13.1 共同点)
- [13.2 不同点总览](#13.2 不同点总览)
- [13.3 最核心区别](#13.3 最核心区别)
- 十四、千万不要混用申请和释放方式
-
- [14.1 malloc 必须配 free](#14.1 malloc 必须配 free)
- [14.2 new 必须配 delete](#14.2 new 必须配 delete)
- [14.3 new\[\] 必须配 delete\[\]](#14.3 new[] 必须配 delete[])
- 十五、常见内存错误总结
-
- [15.1 内存泄漏](#15.1 内存泄漏)
- [15.2 野指针](#15.2 野指针)
- [15.3 重复释放](#15.3 重复释放)
- [15.4 越界访问](#15.4 越界访问)
- [15.5 delete 和 delete\[\] 不匹配](#15.5 delete 和 delete[] 不匹配)
- [十六、现代 C++ 中怎么更安全地管理内存?](#十六、现代 C++ 中怎么更安全地管理内存?)
-
- [16.1 尽量少手动 new/delete](#16.1 尽量少手动 new/delete)
- [16.2 手动管理内存时的基本习惯](#16.2 手动管理内存时的基本习惯)
- 十七、本文总结
一、C/C++ 程序的内存区域划分
1.1 为什么要先理解内存分区?
在讲 malloc、new 之前,必须先知道程序运行时内存大致分成哪些区域。
因为不同变量的生命周期、访问方式和释放方式都不一样。
比如:
cpp
int globalVar = 1;
void Test()
{
int localVar = 1;
int* p = (int*)malloc(sizeof(int));
}
这里的 globalVar、localVar、p、*p 并不在同一个地方。
如果不理解内存区域,后面学习指针、动态内存、对象生命周期时就很容易乱。
1.2 常见内存区域
一个 C/C++ 程序运行时,常见内存区域可以粗略分为:

这里先抓住四个最常见的区域:
- 栈
- 堆
- 数据段
- 代码段 / 常量区
二、栈区:局部变量主要生活的地方
2.1 栈区存什么?
栈区主要存放:
- 非静态局部变量
- 函数参数
- 函数调用过程中的临时数据
- 返回值相关信息
例如:
cpp
void Test()
{
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
}
这里:
cpp
localVar
num1
char2
这些都是函数内部的局部变量,一般位于栈区。
2.2 栈区的特点
栈区有几个特点:
第一,空间由系统自动管理。
局部变量进入作用域时创建,离开作用域时自动销毁。
cpp
void Func()
{
int x = 10;
}
当 Func() 结束时,x 的生命周期也结束。
第二,栈空间通常比较小。
所以不建议在栈上创建特别大的数组。
例如:
cpp
int arr[100000000];
这种写法可能导致栈溢出。
第三,栈的生命周期比较短。
局部变量离开作用域后就不能再使用。
这也是为什么不能返回局部变量地址:
cpp
int* Bad()
{
int x = 10;
return &x; // 错误:x 离开函数后就销毁了
}
三、堆区:动态内存申请的地方
3.1 堆区存什么?
堆区用于程序运行期间的动态内存分配。
比如:
cpp
int* p = (int*)malloc(sizeof(int) * 4);
这里要分清楚:
cpp
p
是局部指针变量,通常在栈上。
cpp
*p
也就是 p 指向的那块动态申请的空间,在堆上。
这点非常重要。
指针变量本身和指针指向的空间,不一定在同一个内存区域。
3.2 堆区的特点
堆区有几个特点:
第一,空间由程序员主动申请。
C 语言用:
cpp
malloc
calloc
realloc
C++ 常用:
cpp
new
new[]
第二,空间需要主动释放。
C 语言用:
cpp
free
C++ 常用:
cpp
delete
delete[]
第三,堆空间生命周期更灵活。
只要你不释放,它就一直存在。
但这也带来了风险:
- 忘记释放,造成内存泄漏;
- 重复释放,造成程序崩溃;
- 释放后继续访问,形成野指针;
- 申请和释放方式不匹配,导致未定义行为。
四、数据段和代码段
4.1 数据段 / 静态区
数据段通常存放:
- 全局变量
- 静态全局变量
- 静态局部变量
比如:
cpp
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
}
这里:
cpp
globalVar
staticGlobalVar
staticVar
都属于静态存储期对象,一般放在数据段 / 静态区。
它们的生命周期不是函数调用期间,而是整个程序运行期间。
比如 staticVar 虽然写在函数内部,但它不是普通局部变量。
cpp
void Test()
{
static int staticVar = 1;
}
staticVar 只初始化一次,并且生命周期持续到程序结束。
4.2 代码段 / 常量区
代码段主要存放:
- 程序可执行代码
- 只读常量
- 字符串字面量等
例如:
cpp
const char* pChar3 = "abcd";
这里要分清楚:
cpp
pChar3
是一个局部指针变量,通常在栈上。
而:
cpp
*pChar3
指向的是字符串字面量 "abcd" 中的字符,这个字符串字面量通常位于只读常量区。
这也是为什么下面这种写法是危险的:
cpp
char* p = "abcd";
p[0] = 'x'; // 错误,不应该修改字符串常量
更推荐写成:
cpp
const char* p = "abcd";
用 const 明确告诉自己和编译器:这块内容不应该被修改。

五、一道经典内存分布题
看下面代码:
cpp
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
逐个分析。
5.1 全局和静态变量
cpp
globalVar
staticGlobalVar
staticVar
它们都位于数据段 / 静态区。
原因:
globalVar是全局变量;staticGlobalVar是静态全局变量;staticVar是静态局部变量。
虽然 staticVar 写在函数里面,但它的生命周期不是函数调用期间,而是整个程序运行期间。
5.2 普通局部变量和数组
cpp
localVar
num1
char2
它们是普通局部变量,通常位于栈区。
需要注意的是:
cpp
char char2[] = "abcd";
这里 char2 是一个数组。
数组本身在栈上,里面存放了字符:
cpp
'a' 'b' 'c' 'd' '\0'
所以:
cpp
char2
在栈上。
cpp
*char2
访问的是数组第一个元素,也在栈上。
5.3 指针变量和字符串常量
cpp
const char* pChar3 = "abcd";
这里分两部分看:
cpp
pChar3
是局部指针变量,通常在栈上。
cpp
*pChar3
访问的是字符串字面量 "abcd" 的第一个字符,通常在代码段 / 常量区。
所以不要把指针变量和它指向的内容混为一谈。
5.4 指针变量和堆空间
cpp
int* ptr1 = (int*)malloc(sizeof(int) * 4);
这里同样分两部分看:
cpp
ptr1
是局部指针变量,通常在栈上。
cpp
*ptr1
访问的是 malloc 申请出来的堆空间。
所以:
ptr1在栈上;*ptr1在堆上。
理解这一点,是学动态内存的关键。
六、C 语言动态内存管理:malloc/calloc/realloc/free
6.1 malloc
malloc 用于从堆上申请一块指定字节数的空间。
cpp
int* p = (int*)malloc(sizeof(int) * 10);
它的特点是:
- 参数是字节数;
- 返回值是
void*; - 使用时通常需要强制类型转换;
- 申请失败返回
NULL; - 不会初始化申请到的空间。
所以申请后通常要判空:
cpp
int* p = (int*)malloc(sizeof(int) * 10);
if (p == NULL)
{
perror("malloc fail");
return;
}
6.2 calloc
calloc 也用于动态申请空间。
cpp
int* p = (int*)calloc(10, sizeof(int));
它和 malloc 的主要区别是:
calloc 会把申请到的空间初始化为 0。
参数也不同:
cpp
calloc(元素个数, 每个元素大小)
而 malloc 是:
cpp
malloc(总字节数)
所以:
cpp
calloc(10, sizeof(int))
表示申请 10 个 int 大小的空间,并把这些字节清零。
6.3 realloc
realloc 用于调整已经申请过的动态内存大小。
cpp
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
这里表示:
把原来 p2 指向的空间调整成可以存放 10 个 int 的空间。
realloc 有两种情况。
第一种,原地扩容成功。
原来的地址不变。
第二种,原地空间不够。
系统会重新找一块更大的空间,把旧数据拷贝过去,再释放旧空间。
所以 realloc 的返回值一定要接住。

6.4 realloc 的安全写法
很多人会直接写:
cpp
p = (int*)realloc(p, newSize);
这有风险。
如果 realloc 失败,会返回 NULL。
这时原来的空间并不会自动释放,但 p 已经被改成了 NULL,原地址丢失,造成内存泄漏。
更安全的写法是:
cpp
int* tmp = (int*)realloc(p, newSize);
if (tmp == NULL)
{
perror("realloc fail");
// p 仍然有效,可以继续使用或之后释放
}
else
{
p = tmp;
}
这段代码很值得记住。
6.5 free
free 用于释放 malloc、calloc、realloc 申请的空间。
cpp
free(p);
p = NULL;
释放后最好把指针置空。
原因是:
cpp
free(p);
只是释放了 p 指向的空间,并不会自动把 p 变成空指针。
如果后面继续使用 p,就会形成野指针。
所以推荐写:
cpp
free(p);
p = NULL;
七、malloc、calloc、realloc 的区别
可以用一张表总结:
| 函数 | 作用 | 是否初始化 | 参数特点 |
|---|---|---|---|
malloc |
申请指定字节数空间 | 不初始化 | malloc(size) |
calloc |
申请多个元素空间 | 初始化为 0 | calloc(count, size) |
realloc |
调整已有空间大小 | 新增空间不一定初始化 | realloc(ptr, newSize) |
free |
释放动态空间 | 不涉及 | free(ptr) |
简单记:
malloc:只申请,不清空;calloc:申请并清零;realloc:调整已有空间;free:释放空间。
八、C++ 为什么还要引入 new/delete?
8.1 malloc/free 在 C++ 中还能用吗?
能用。
C++ 兼容 C 的很多用法,所以 malloc/free 在 C++ 中仍然可以使用。
但是 C++ 有类和对象。
对于自定义类型来说,光申请一块空间还不够。
还要调用构造函数完成对象初始化。
同样,释放对象前也要调用析构函数清理资源。
而 malloc/free 不会自动调用构造函数和析构函数。
这就是 C++ 引入 new/delete 的重要原因。
8.2 new/delete 的基本用法
申请单个对象:
cpp
int* p1 = new int;
申请单个对象并初始化:
cpp
int* p2 = new int(10);
申请数组:
cpp
int* p3 = new int[10];
释放时要匹配:
cpp
delete p1;
delete p2;
delete[] p3;
注意:
申请单个对象,用:
cpp
new
delete
申请连续数组,用:
cpp
new[]
delete[]
二者必须匹配使用。
九、new/delete 操作自定义类型
9.1 malloc/free 只管空间
先定义一个类:
cpp
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
如果写:
cpp
A* p1 = (A*)malloc(sizeof(A));
free(p1);
这只发生了两件事:
- 申请一块和
A对象一样大的空间; - 释放这块空间。
但是:
- 构造函数不会调用;
- 析构函数不会调用。
严格来说,p1 指向的只是"一块原始内存",还不能算一个真正完成构造的 A 对象。
9.2 new/delete 会调用构造和析构
如果写:
cpp
A* p2 = new A(1);
delete p2;
过程就不同了。
new A(1) 会做两件事:
- 申请空间;
- 在这块空间上调用构造函数,构造对象。
delete p2 也会做两件事:
- 调用析构函数,清理对象内部资源;
- 释放对象占用的空间。
这就是 new/delete 和 malloc/free 在自定义类型上的最大区别。

9.3 数组对象也一样
对于数组:
cpp
A* p3 = (A*)malloc(sizeof(A) * 10);
free(p3);
不会调用 10 次构造函数和析构函数。
而:
cpp
A* p4 = new A[10];
delete[] p4;

会:
- 创建时调用 10 次构造函数;
- 释放时调用 10 次析构函数。
所以管理自定义类型对象时,应该优先使用 new/delete,而不是 malloc/free。
不过在现代 C++ 中,更进一步的建议是:
能不用手动 new/delete,就尽量使用标准库容器和智能指针管理资源。
例如:
cpp
std::vector<int> v;
std::unique_ptr<A> p;
这类工具能减少内存泄漏和手动释放错误。
十、operator new 和 operator delete 是什么?
10.1 new 和 operator new 不是一回事
很多初学者会把 new 和 operator new 混在一起。
它们不是一个东西。
new 是操作符,也叫 new 表达式。
例如:
cpp
A* p = new A(10);
它会完成完整的对象创建过程:
- 调用
operator new申请空间; - 调用构造函数初始化对象。
而 operator new 是一个函数。
它只负责申请原始内存,不负责调用构造函数。
可以粗略理解为:
cpp
new = operator new + 构造函数
10.2 delete 和 operator delete 也不是一回事
同理:
cpp
delete p;
是一个完整的删除表达式。
它会做两件事:
- 调用析构函数;
- 调用
operator delete释放空间。
而 operator delete 只是释放原始空间的函数,不负责调用析构函数。
可以粗略理解为:
cpp
delete = 析构函数 + operator delete

10.3 operator new 底层通常还是 malloc
在很多实现中,operator new 底层也会调用类似 malloc 的机制申请空间。
但二者的失败处理方式不同。
malloc 失败通常返回 NULL。
普通 new 失败通常抛出 std::bad_alloc 异常。
这也是为什么下面两种代码处理方式不同:
cpp
int* p = (int*)malloc(sizeof(int));
if (p == NULL)
{
// 申请失败
}
而:
cpp
try
{
int* p = new int;
}
catch (const std::bad_alloc& e)
{
// 申请失败
}
当然,也可以使用 nothrow new 让 new 失败时返回空指针,但入门阶段先掌握普通 new 的异常机制即可。
十一、new/delete 的底层原理
11.1 new 单个对象的过程
对于:
cpp
A* p = new A(10);
底层可以理解成:
- 调用
operator new(sizeof(A))申请空间; - 在申请到的空间上调用
A的构造函数; - 返回对象指针。
所以 new 不只是申请内存,它还负责构造对象。
11.2 delete 单个对象的过程
对于:
cpp
delete p;
底层可以理解成:
- 调用
A的析构函数; - 调用
operator delete(p)释放空间。
所以 delete 不只是释放内存,它还负责销毁对象。
11.3 new\[\] 的过程
对于:
cpp
A* p = new A[10];
底层可以理解成:
- 调用
operator new[]申请足够容纳 10 个对象的空间; - 在这块空间上依次调用 10 次构造函数。
也就是说,new[] 要构造多个对象。
11.4 delete\[\] 的过程
对于:
cpp
delete[] p;
底层可以理解成:
- 对数组中的每个对象调用析构函数;
- 调用
operator delete[]释放整块空间。
所以 new[] 必须配 delete[]。
如果用错:
cpp
A* p = new A[10];
delete p; // 错误
可能只析构部分对象,甚至导致更严重的问题。
十二、定位 new:在已有空间上构造对象
12.1 什么是定位 new?
定位 new,也叫 placement new。
它的作用是:
在一块已经分配好的原始内存上,显式调用构造函数创建对象。
语法:
cpp
new(place_address) Type;
new(place_address) Type(initializer-list);
比如:
cpp
A* p = (A*)malloc(sizeof(A));
new(p) A(10);
第一句:
cpp
malloc(sizeof(A))
只是申请了一块原始空间。
第二句:
cpp
new(p) A(10);
才是在这块空间上调用构造函数,把它变成一个真正的 A 对象。

12.2 定位 new 的完整释放流程
使用定位 new 后,释放时也要分两步。
cpp
A* p = (A*)malloc(sizeof(A));
new(p) A(10); // 在已有空间上构造对象
p->~A(); // 手动调用析构函数
free(p); // 释放原始空间
注意:
定位 new 不负责申请空间。
所以也不能直接用普通 delete 去释放。
因为空间是 malloc 来的,就应该用 free 释放。
但在 free 前,必须手动调用析构函数。
12.3 定位 new 用在哪里?
入门阶段只需要知道它的用途即可。
定位 new 常见于:
- 内存池;
- 对象池;
- 自定义容器;
- 高性能场景下的对象构造控制。
比如内存池提前申请一大块原始内存。
当需要创建对象时,不再频繁向系统申请小块内存,而是在已有内存上用定位 new 构造对象。
这可以减少频繁分配释放的开销。
十三、malloc/free 和 new/delete 的区别
13.1 共同点
它们的共同点是:
都可以从堆上申请空间,并且都需要程序员释放。
例如:
cpp
int* p1 = (int*)malloc(sizeof(int));
free(p1);
int* p2 = new int;
delete p2;
这两组都在动态管理堆空间。
13.2 不同点总览
| 对比项 | malloc/free | new/delete |
|---|---|---|
| 本质 | 库函数 | C++ 操作符 |
| 申请大小 | 需要手动计算字节数 | 根据类型自动计算 |
| 返回类型 | void*,通常要强转 |
返回对应类型指针 |
| 初始化 | 不会自动初始化 | 可以初始化 |
| 失败处理 | 返回 NULL |
默认抛异常 |
| 自定义类型 | 不调用构造/析构 | 调用构造/析构 |
| 数组申请 | 手动计算总大小 | 使用 new[] |
| 释放方式 | free |
delete/delete[] |
13.3 最核心区别
对于内置类型:
cpp
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
二者差别相对小一些。
但对于自定义类型:
cpp
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A;
差别就很大。
malloc 只申请空间。
new 申请空间并调用构造函数。
对应释放时:
free 只释放空间。
delete 先调用析构函数,再释放空间。
这就是核心区别。
十四、千万不要混用申请和释放方式
14.1 malloc 必须配 free
正确:
cpp
int* p = (int*)malloc(sizeof(int));
free(p);
错误:
cpp
int* p = (int*)malloc(sizeof(int));
delete p; // 错误
14.2 new 必须配 delete
正确:
cpp
int* p = new int;
delete p;
错误:
cpp
int* p = new int;
free(p); // 错误
14.3 new\[\] 必须配 delete\[\]
正确:
cpp
int* p = new int[10];
delete[] p;
错误:
cpp
int* p = new int[10];
delete p; // 错误
可以记一句话:
谁申请,谁释放;怎么申请,就怎么释放。
十五、常见内存错误总结
15.1 内存泄漏
申请了空间,但忘记释放。
cpp
void Func()
{
int* p = new int[10];
// 忘记 delete[]
}
函数结束后,p 这个局部指针变量销毁了。
但它指向的堆空间还在。
这块空间再也找不到了,就形成内存泄漏。
15.2 野指针
释放空间后继续使用。
cpp
int* p = new int(10);
delete p;
cout << *p << endl; // 错误
delete p 后,p 指向的空间已经无效。
但 p 自己的值还在。
这时继续使用 p,就是野指针访问。
建议释放后置空:
cpp
delete p;
p = nullptr;
15.3 重复释放
同一块空间释放两次。
cpp
int* p = new int;
delete p;
delete p; // 错误
第一次 delete 后,空间已经被释放。
第二次继续释放,行为未定义。
释放后置空可以降低风险:
cpp
delete p;
p = nullptr;
delete p; // 对 nullptr 执行 delete 是安全的
15.4 越界访问
申请 10 个元素,却访问第 10 个下标。
cpp
int* p = new int[10];
p[10] = 1; // 错误,合法下标是 0~9
delete[] p;
越界访问可能不会立刻报错,但会破坏其他内存,属于非常危险的问题。

15.5 delete 和 delete\[\] 不匹配
cpp
A* p = new A[10];
delete p; // 错误
对于自定义类型数组,这种错误尤其严重。
因为 delete[] 需要正确调用多个对象的析构函数。
如果写错,可能导致析构不完整、内存破坏或程序崩溃。
十六、现代 C++ 中怎么更安全地管理内存?
16.1 尽量少手动 new/delete
虽然本文重点讲 new/delete,但在真实工程中,现代 C++ 更推荐使用 RAII 思想管理资源。
简单说:
让对象在构造时获取资源,在析构时释放资源。
比如:
cpp
std::vector<int> v;
vector 会自己管理动态数组空间。
你不需要手动 new[] 和 delete[]。
再比如:
cpp
std::unique_ptr<A> p = std::make_unique<A>();
unique_ptr 会在生命周期结束时自动释放对象。
这能减少很多内存泄漏和异常路径下忘记释放的问题。
16.2 手动管理内存时的基本习惯
如果必须手动管理内存,建议遵守下面几条:
第一,申请后立刻判断是否成功。
对于 malloc:
cpp
int* p = (int*)malloc(sizeof(int) * n);
if (p == NULL)
{
return;
}
第二,释放后立刻置空。
cpp
free(p);
p = NULL;
或:
cpp
delete p;
p = nullptr;
第三,申请和释放方式必须匹配。
cpp
malloc -> free
new -> delete
new[] -> delete[]
第四,自定义类型优先用 new/delete,不要用 malloc/free 直接处理对象。
第五,能用标准库容器,就不要自己管理动态数组。
十七、本文总结
本文主要梳理了 C++ 内存管理的核心内容。
第一,C/C++ 程序内存大致可以分为栈、堆、数据段、代码段和内存映射段。
第二,普通局部变量通常在栈上,全局变量和静态变量通常在数据段,动态申请的空间在堆上,字符串常量通常在只读常量区。
第三,指针变量本身和它指向的空间可能位于不同区域。比如局部指针变量在栈上,但它指向的动态空间在堆上。
第四,malloc/calloc/realloc/free 是 C 语言动态内存管理方式。
第五,malloc 只申请空间,不初始化;calloc 会清零;realloc 用于调整已有空间大小;free 用于释放空间。
第六,C++ 中 new/delete 不仅管理空间,还会处理对象构造和析构。
第七,new 和 operator new 不是一回事。operator new 只负责申请原始空间,new 表达式还会调用构造函数。
第八,delete 和 operator delete 也不是一回事。delete 表达式会先调用析构函数,再释放空间。
第九,定位 new 用于在已经分配好的原始内存上显式构造对象,常见于内存池等场景。
第十,malloc/free、new/delete、new[]/delete[] 必须匹配使用,不能混用。