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 / deletemalloc / free
本质运算符库函数
是否调用构造函数/析构函数不会
返回类型类型安全(返回具体类型指针)void*,需强转
失败处理抛出 std::bad_alloc返回 NULL
可重载可(作为运算符重载)不可
数组分配new[] / delete[]malloc(size)
C++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 允许在已分配的指定内存地址上构造对象,常用于内存池、自定义容器等场景。

C++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 起):

C++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 编译选项等。