1. C/C++内存分布
在C/C++的内存结构中, 由低地址到高地址依次为:
-
代码段:存放程序的机器指令与只读常量
-
数据段:存放全局变量与静态变量
-
堆区:手动动态分配的内存(如 new, malloc)
-
内存映射段:映射文件或设备,支持共享内存等机制
-
栈区:函数调用时自动分配的局部变量和返回地址
-
内核空间:操作系统使用的区域,用户程序无法直接访问

2. C语言动态内存管理方式
在了解了内存分布后,我们来看看 C 语言是如何管理内存的。
通过: stdlib.h 中的 malloc, calloc, realloc, free 等函数手动管理。
malloc() | 分配指定字节数的内存,内容不初始化 |
calloc() | 分配并初始化为0,参数为元素个数和大小 |
realloc() | 重新调整已分配内存的大小 |
free() | 释放分配的内存 |
代码示例:
int* p = (int*)malloc(sizeof(int) * 5); // 分配五个int内存
// 如果 malloc分配失败, 则返回空指针
if(p == NULL)
{
perror("malloc:"); // 输出错误
return;
}
free(p); // 释放内存
在使用动态内存分配时, 我们应该注意内存泄漏, 重复释放, 越界访问等问题
3. C++动态内存管理方式
相较于 C 语言直接使用 malloc/free 管理内存,C++ 提供了更高级、更安全的内存管理方式,通过 new/delete 来进行内存管理
3.1 内置类型的动态内存管理
C++ 中对内置类型 (int, double, char...) 的动态分配本质上与 C 语言类似,但引入了 new 语法,具有更好的类型安全性。
代码示例:
// 动态申请一个 int类型的内存空间
int* ptr1 = new int;
// 动态申请一个 char类型的空间并初始化为 'a'
char* ptr2 = new char('a');
//动态申请一个 double类型数组, 未初始化
double* ptr3 = new double[5];
// 动态申请一个 long类型数组, 并初始化
long ptr4 = new long[5]{1, 2, 3, 4, 5};
delete ptr1; // 释放空间
delete ptr2;
delete[] ptr3; // 释放数组时要使用 delete[]
delete[] ptr4;
与 C 语言的动态内存管理不同,C++ 的 new运算符在申请内存时,能够直接返回对应类型的指针,无需进行类型强制转换。同时,它还支持灵活的初始化方式,而 C 语言中的 malloc 和calloc 无法实现自定义初始化,前者不初始化,后者仅能初始化为零。
注意: 当我们使用 new[]申请连续的内存时一定要使用 delete[]来释放, 需要匹配使用, 否则会出现未定义的行为
3.2 自定义类型的动态内存管理
自定义类型 (结构体, 类) 在分配时除了需要分配空间, 还需要调用对应的构造函数, 在释放时也要调用析构函数, 这也是 C 和 C++ 内存管理的本质区别之一
代码示例:
class MyClass
{
public:
MyClass(int a)
:_a(a)
{
cout << "MyClass 的构造函数调用了" << endl;
}
~MyClass()
{
cout << "MyClass 的析构函数调用了" << endl;
}
private:
int _a;
};
int main()
{
MyClass* obj = new MyClass(10); // 调用构造函数对类的成员进行初始化
delete obj; // 释放时调用类的析构函数
return 0;
}
运行结果:
MyClass 的构造函数调用了
MyClass 的析构函数调用了
相比 malloc/free , new/delete 在申请内置类型时支持初始化,在处理自定义类型时能自动调用构造和析构函数。不仅更安全,也更符合 C++ 的设计哲学,可看作是对 malloc/free 的语法上的升级。
4. operator new 和 operator delete
operator new 和 operator delete 是系统提供的全局函数, new/delete 关键字在底层会分别调用这两个函数来分配释放空间
void* operator new(std::size_t size); // 分配内存
void operator delete(void* ptr) noexcept; // 释放内存
当我们使用 new 申请自定义类型空间时:
MyClass* ptr1 = new MyClass(10);
实际等价于:
void* mem = operator new(sizeof(MyClass)); // 分配内存
MyClass* ptr1 = new(mem) MyClass(10); // 在内存上构建对象
而 delete:
delete ptr1;
实际上等价于:
ptr1->~MyClass(); // 调用析构函数
operator delete(ptr1); // 清理动态申请的内存
4.1 operator new 的底层原理
C++ 中的 operator new 本质上是对 malloc 的封装,它在底层实际调用了 malloc,当分配失败时尝试调用 _callnewh(内存不足时的处理),最终可能抛出 bad_alloc 异常。
以下是核心流程的简化示例:
void* operator new(std::size_t size) {
void* p;
while ((p = malloc(size)) == nullptr) {
if (_callnewh(size) == 0) {
throw std::bad_alloc(); // 分配失败时抛出异常
}
}
return p;
}
可以看到, operator new 是更安全的, 它在内存不足时会抛出异常来提示使用者
4.2 operator delete 的底层原理
operator delete 实际上最终调用了 free
其实现类似于:
void operator delete(void* ptr)
{
if(ptr == nullptr)
return;
free(ptr); // 调用free释放内存
}
4.3 面试经典题目
全局 operator new 和类内重载的区别?
全局 operator new 是默认的内存分配函数,适用于所有对象;类内重载的 operator new 则只影响该类的对象分配方式。
我们可以为某个类单独提供内存分配和释放逻辑:
class MyClass
{
void* operator new(size_t size)
{
cout << "MyClass 的operator new 调用了" << endl;
return malloc(size);
}
void operator delete(void* ptr)
{
if(ptr == nullptr)
return;
cout << "MyClass 的operator delete 调用了" <<endl;
free(ptr);
}
private:
int _a;
}
如果类中提供了自己的 operator new,那么用 new 创建该类对象时将优先调用类内版本,不会再用全局版本, 类内也可以重载多个版本。
operator new[] , operator delete[] 也可以进行重载
5. operator new[] 和 operator delete[]
5.1 new T[] , delete[] 的原理
| new T[] |
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请 2. 在申请的空间上执行N次构造函数 |
| delete[] |
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理 2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间 |
5.2 为什么new T[]申请的内存一定要使用delete[]释放?
C++ 中的 new[] 做了 额外的隐藏操作,不仅仅是调用 operator new[] 分配数组空间,还偷偷加了头部信息。
当我们这样使用时:
MyClass* ptr = new MyClass[3];
内存分布可能是这样的:

在对象数组前,编译器通常会额外插入一段头部信息,以便在 delete[] 时能正确地调用每个元素的析构函数。
- 指针 ptr 实际指向的是第一个对象,而不是整块内存的起始位置。
- 只有 delete[] 能正确识别并回退这段头部信息,从而完整析构所有对象并释放内存。
如果使用 delete 来释放 new[] 分配的内存,将无法找到头部信息,导致释放地址不正确 (跳过了头部),可能引发内存泄漏甚至会导致程序崩溃
因此,new[] 必须与 delete[] 成对使用,绝不可与 delete 混用。
6. 定位 new
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type 或者 new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
代码示例:
// ptr 现在指向的只不过是与 MyClass对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
// 有执行
MyClass* ptr = (MyClass*)malloc(sizeof(MyClass));
new(ptr) MyClass(10); // 在空间上构造对象
ptr->~MyClass();
free(ptr)
定位 new 的使用场景极少, 了解即可
7. 总结 new/delete 和 malloc/free 的区别
malloc/free 和 new/free 都是从堆上申请空间, 并且都需要手动释放, 不同的地方是:
1. malloc/free是函数,new/delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型,如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要, 直接返回对应的类型
5. malloc申请空间失败时,返回的是NULL,new申请失败会抛异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成资源的释放
8. 结语
理解内存管理的本质,并不仅仅是为了掌握 new 和 delete 的语法,而是为了写出更安全、更具工程能力的 C++ 代码。随着语言的发展,RAII、智能指针、标准容器已经逐渐替代了手动内存管理。但理解背后的机制,仍然是每一个 C++ 开发者的必修课。

12万+

被折叠的 条评论
为什么被折叠?



