C++ new 分配的内存一定在堆上吗?

最近我突然想到 placement new 可以直接在指定位置直接构造对象而不分配内存,于是有了这篇文章。

1. 一般情况下,new 在堆上分配内存

在大多数情况下,new 确实会在 堆(heap) 上分配内存:

int* p = new int(42); // 在堆上分配内存
  • 堆内存 由 C++ 运行时库(如 malloc/free)管理。
  • 必须手动释放,否则会导致内存泄漏:
    delete p; // 释放堆内存

2. new 还可以在栈上分配内存

C++ 提供了 placement new(定位 new),允许在指定的内存地址上构造对象,而 不分配新内存。因此,我们可以让 new 在栈上分配内存:

#include <new>

void demo() {
    alignas(int) char buffer[sizeof(int)]; // 栈上的内存
    int* p = new (buffer) int(42); // 在栈上构造 int

    std::cout << *p << std::endl; // 输出 42

    // 不需要 delete,但要手动调用析构函数(如果是类对象)
    p->~int(); // 对于 int 这种 trivial 类型,可以省略
}

关键点

  • buffer 是栈上的数组,new (buffer) int 只是在该地址构造对象。
  • 不能使用 delete p,因为内存是栈上的,delete 会尝试释放堆内存,导致未定义行为(UB)。
  • 必须确保内存对齐(如 alignas(int)),否则某些 CPU 架构(如 ARM)会崩溃。

3. new 还可以在其他存储区域分配内存

除了堆和栈,new 还可以在 自定义内存池共享内存硬件寄存器映射内存 上分配内存,只要提供正确的地址即可。

示例:在静态存储区分配

#include <new>

char static_buffer[sizeof(int)]; // 静态存储区(全局变量)

int main() {
    int* p = new (static_buffer) int(100); // 在静态区构造对象
    std::cout << *p << std::endl;
    p->~int();
    return 0;
}
  • static_buffer 位于程序的 数据段(data segment),而不是堆或栈。

示例:在内存池中分配

#include <new>
#include <vector>

class MemoryPool {
public:
    void* allocate(size_t size) {
        return pool.data(); // 返回内存池地址
    }
private:
    std::vector<char> pool{1024}; // 模拟内存池
};

int main() {
    MemoryPool pool;
    int* p = new (pool.allocate(sizeof(int))) int(200); // 在内存池构造
    std::cout << *p << std::endl;
    p->~int();
    return 0;
}
  • 这里 new 在自定义的内存池上分配,而不是堆或栈。

4. 为什么需要 placement new

(1)避免堆分配的开销

  • 高频小对象(如游戏中的粒子系统)可以使用栈或内存池,减少 new/delete 的代价。

    (2)控制对象的内存位置

  • 嵌入式系统可能需要将对象放在特定地址(如硬件寄存器)。
  • 共享内存(IPC)需要在固定地址构造对象。

    (3)实现自定义内存管理

  • 内存池、对象池等技术依赖 placement new 在预分配的内存上构造对象。

因此,new 分配的内存不一定在堆上,它的行为取决于具体的使用方式。理解这一点有助于优化性能并避免未定义行为。