C++ 程序运行期间内存分配整理
[toc]
new 内存分配
在C++中,使用new关键字分配的内存是在虚拟内存中。虚拟内存是一种抽象概念,它允许程序以连续且固定大小的内存地址空间运行,而不管物理内存的实际配置如何。
当你使用new在C++中创建一个对象或数组时,内存管理器会在堆内存中分配一块空间,并返回指向这块空间的指针。即使这块空间超出了程序当前的实际物理内存,操作系统也会通过页面调度和内存映射机制来确保程序的正确运行。
因此,使用new分配的内存是在虚拟内存中,但操作系统会根据物理内存的实际配置进行管理。
C++ 中的内存区域分布
一个典型的 C++ 程序,其内存在逻辑上被划分为以下几个区域:
| 区域 | 存放内容 | 分配/释放 |
|---|---|---|
| 代码段(Text) | 编译后的机器指令 | 只读,不可修改 |
| 数据段(Data) | 已初始化的全局/静态变量 | 编译期确定,程序结束释放 |
| BSS 段 | 未初始化的全局/静态变量(自动清零) | 编译期确定,程序结束释放 |
| 堆(Heap) | new/malloc 动态分配的对象 | 运行时手动分配/释放 |
| 栈(Stack) | 局部变量、函数参数、返回地址 | 函数调用时自动分配/释放 |
栈内存由编译器自动管理,速度快但容量有限(默认约 1~8MB);堆内存由程序员手动管理,容量大但需要
delete/free释放,否则会造成内存泄漏。
new / delete 与 malloc / free 的区别
| 对比项 | new / delete | malloc / free |
|---|---|---|
| 本质 | 运算符 | 库函数 |
| 是否调用构造函数/析构函数 | 会 | 不会 |
| 返回类型 | 类型安全(返回具体类型指针) | void*,需强转 |
| 失败处理 | 抛出 std::bad_alloc | 返回 NULL |
| 可重载 | 可(作为运算符重载) | 不可 |
| 数组分配 | new[] / delete[] | malloc(size) |
cpp
// new/delete 会自动调用构造/析构
Foo* p = new Foo();
delete p;
// malloc/free 只分配/释放原始字节,不调用构造/析构
Foo* q = (Foo*)malloc(sizeof(Foo));
new(q) Foo(); // placement new 手动构造
q->~Foo(); // 手动析构
free(q);placement new(定位 new)
placement new 允许在已分配的指定内存地址上构造对象,常用于内存池、自定义容器等场景。
cpp
#include <new>
void* buf = operator new(sizeof(Foo));
Foo* p = new (buf) Foo(); // 在 buf 指向的内存上构造
p->~Foo(); // 手动调用析构
operator delete(buf); // 释放原始内存注意:
placement new分配的内存不能用delete释放,需要手动调用析构函数,再释放底层内存。
内存泄漏与智能指针
避免内存泄漏最有效的方式是尽量使用智能指针(C++11 起):
cpp
#include <memory>
std::unique_ptr<Foo> u = std::make_unique<Foo>(); // 独占所有权
std::shared_ptr<Foo> s = std::make_shared<Foo>(); // 共享所有权(引用计数)
std::weak_ptr<Foo> w = s; // 弱引用,不增加计数unique_ptr:独占所有权,超出作用域自动释放,不可拷贝只可移动。shared_ptr:引用计数归零时自动释放,适合多所有权场景。weak_ptr:配合shared_ptr使用,可打破循环引用,避免内存泄漏。
排查工具:
valgrind(Linux)、Visual Studio 的 CRT 内存泄漏检测、/we编译选项等。