C++内存管理知识点:new/delete与动态内存
一、前言
学好 C++ 内存管理是写出高质量代码的关键。相比 C 语言的 malloc/free,C++ 提供了更强大、更安全的 new/delete 机制。这期来全面了解 C/C++ 的内存分布、new/delete 的用法以及常见的内存问题。
二、C/C++ 内存分布
一个 C/C++ 程序运行时,内存被划分为以下几个区域:
| 区域 | 存放内容 | 分配方式 | 生命周期 |
|---|---|---|---|
| 栈 (Stack) | 局部变量、函数参数、返回地址 | 自动分配释放 | 函数结束即释放 |
| 堆 (Heap) | 动态申请的内存(malloc/new) | 手动申请释放 | 程序员控制 |
| 全局/静态区 (Data Segment) | 全局变量、static 变量 | 程序启动分配 | 程序结束释放 |
| 常量区 (.rodata) | 字符串常量、const 全局量 | 编译时确定 | 只读,程序结束释放 |
| 代码区 (.text) | 函数代码(二进制指令) | 编译时确定 | 只读,程序结束释放 |
int global_var = 0; // 全局/静态区
static int static_var = 1; // 全局/静态区
const int const_global = 2; // 常量区
void Func() {
int local_var = 3; // 栈区
static int func_static = 4; // 全局/静态区
int* p = new int(5); // p 在栈,指向的内容在堆
const char* str = "hello"; // str 在栈,指向常量区
delete p;
}
三、new / delete 的基本用法
C++ 的 new/delete 比 malloc/free 更强大,它会自动调用构造和析构函数。
// 单个对象
int* p1 = new int; // 不初始化(值是随机值)
int* p2 = new int(10); // 初始化为 10
int* p3 = new int(); // 值初始化为 0
int* p4 = new int{}; // C++11 列表初始化
delete p1; // 释放单个对象
delete p2;
// 数组
int* arr1 = new int[10]; // 不初始化
int* arr2 = new int[10](); // 所有元素初始化为 0
int* arr3 = new int[10]{1,2,3}; // 前三个为 1,2,3,其余为 0
delete[] arr1; // 释放数组必须用 delete[]
delete[] arr2;
delete[] arr3;
// 自定义类型——new 会调用构造函数
class A {
public:
A(int x = 0) : _x(x) { cout << "构造 " << _x << endl; }
~A() { cout << "析构 " << _x << endl; }
private:
int _x;
};
A* pa = new A(42); // 输出:构造 42
delete pa; // 输出:析构 42
A* arr = new A[3]; // 输出三次:构造 0
delete[] arr; // 输出三次:析构 0
注意:
new[]必须搭配delete[],new必须搭配delete,混用会导致未定义行为!
四、new/delete 与 malloc/free 的详细对比
| 对比维度 | malloc/free | new/delete |
|---|---|---|
| 本质 | 函数(C 标准库) | 运算符(C++ 关键字) |
| 返回值 | void*,需要强转 |
自动类型匹配 |
| 大小 | 需要手动计算字节数 | 编译器自动计算 |
| 自定义类型 | 只开辟空间,不调构造函数 | 开空间 + 调构造函数 |
| 失败处理 | 返回 NULL | 抛出 std::bad_alloc 异常 |
| 数组处理 | 手动计算总大小 | new[] 自动处理 |
| 可重载 | 不可 | 可以(全局或类级别) |
| 扩展功能 | 无 | 支持 placement new |
// malloc 的写法:
int* p1 = (int*)malloc(sizeof(int));
if (p1 == nullptr) { /* 处理错误 */ }
free(p1);
// new 的写法(更简洁):
int* p2 = new int(10); // 不需要强转,不需要 sizeof
delete p2; // 不返回错误码,失败抛异常
工作流程对比:
new内部流程:调用operator new(内部调用malloc)→ 调用构造函数delete内部流程:调用析构函数 → 调用operator delete(内部调用free)
五、定位 new (placement new)
在已分配好的内存上构造对象,不额外申请空间。
#include <new> // 需要包含头文件
// 基本用法
void* mem = operator new(sizeof(A)); // 只分配内存
A* p = new(mem) A(10); // 在已有内存上构造
// 使用对象...
p->~A(); // 手动调用析构
operator delete(mem); // 手动释放内存
// 实用场景:内存池
alignas(A) char buffer[sizeof(A)]; // 栈上空间
A* p2 = new(buffer) A(42); // 在栈上 buffer 构造,不涉及堆分配
p2->~A(); // 手动析构
placement new 的应用场景:
- 内存池(预先分配大块内存,避免频繁的堆分配)
- 嵌入式/实时系统(不允许动态内存分配失败)
- 共享内存中构造对象
六、内存泄漏与检测方法
什么是内存泄漏? 动态分配的内存没有被释放,程序失去对这块内存的掌控。长期运行的程序(如服务器)内存泄漏会导致内存耗尽。
void LeakExample() {
int* p = new int[1000];
// 忘记 delete[] p — 内存泄漏!
// 函数结束,p 指针销毁,但堆上 1000 个 int 永远不会被释放
}
常见内存泄漏场景:
new后忘记deletenew[]用了delete(或反过来)- 异常导致
delete被跳过 - 基类析构函数不是虚函数
检测内存泄漏的方法:
// 方法1:_CrtDumpMemoryLeaks (Windows/MSVC)
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
int main() {
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
int* p = new int(42);
// 不 delete,运行后会在输出窗口显示内存泄漏报告
return 0;
}
// 方法2:使用 Valgrind (Linux)
// $ valgrind --leak-check=full ./program
// 方法3:使用智能指针避免手动管理
#include <memory>
void SafeExample() {
std::unique_ptr<int[]> p(new int[1000]);
// 离开作用域自动 delete,不会泄漏
}
七、RAII 简介
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 管理资源的核心理念:在构造函数中获取资源,在析构函数中释放资源。
// 传统的麻烦方式
void OldWay() {
int* p = new int[100];
// ... 可能抛出异常,跳过 delete
delete[] p; // 异常时执行不到这里
}
// RAII 方式——用对象管理资源
class IntArray {
public:
IntArray(size_t n) : _data(new int[n]), _size(n) {}
~IntArray() { delete[] _data; }
// 拷贝构造、赋值运算符(实现深拷贝,此处省略)
private:
int* _data;
size_t _size;
};
void RAIIWay() {
IntArray arr(100); // 构造函数获取资源
// ... 即使抛出异常,arr 析构时也会释放资源
} // 离开作用域自动调用析构函数
C++ 标准库的智能指针(unique_ptr、shared_ptr、weak_ptr)就是 RAII 思想的典型应用。
八、operator new/delete 的重载
可以全局重载或类级别重载,用于定制内存分配策略。
// 全局重载——影响所有 new/delete
void* operator new(size_t size) {
std::cout << "全局 new,大小:" << size << std::endl;
void* p = malloc(size);
if (!p) throw std::bad_alloc();
return p;
}
void operator delete(void* p) noexcept {
std::cout << "全局 delete" << std::endl;
free(p);
}
// 类级别重载——只影响该类
class MyClass {
public:
void* operator new(size_t size) {
std::cout << "MyClass 专属 new" << std::endl;
return malloc(size);
}
void operator delete(void* p) noexcept {
std::cout << "MyClass 专属 delete" << std::endl;
free(p);
}
};
int main() {
int* p = new int(5); // 调用全局重载
delete p;
MyClass* mp = new MyClass; // 调用类级别重载
delete mp;
return 0;
}
重载的应用场景:
- 内存池(减少碎片、提高分配速度)
- 统计内存使用情况(调试或性能分析)
- 特定对齐要求
九、面试常见问题
Q1:new 和 malloc 的区别是什么?
A:new 是运算符,malloc 是函数;new 自动计算大小、返回正确类型、调用构造函数、失败抛异常;malloc 需手动计算大小、返回 void*、不调用构造函数、失败返回 NULL。
Q2:new[] 和 delete[] 如何知道数组大小?
A:大多数编译器在 new[] 分配的内存块头部额外存储了数组元素个数。delete[] 读取这个值,从而知道要调用多少次析构函数。
Q3:什么是内存泄漏?如何避免?
A:动态分配的内存没有被释放导致资源浪费。避免方法:用 RAII(智能指针)、new/delete 成对出现、使用内存检测工具。
Q4:placement new 有什么用途?
A:在已分配的内存上构造对象,主要用于内存池、嵌入式系统、共享内存等场景,避免频繁堆分配的开销。
十、总结
- C/C++ 内存五区分布及各自特点
- new/delete 的基本用法(单个对象和数组)
- new/delete 与 malloc/free 的全面对比
- placement new 的用法和场景
- 内存泄漏的原因和检测方法
- RAII 的核心思想
- operator new/delete 的重载
内存管理是 C++ 学习的重点也是难点,掌握好这些知识对写出高效、稳定的程序至关重要。下期进入模板初阶!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)