Menu

C++ 动态内存:new 和 delete 详解

如何在运行时用 new 分配内存、用 delete 释放内存,以及如何避开手动管理堆内存时常见的内存泄漏、悬空指针和重复释放。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

为什么要在运行时申请内存

到目前为止,你创建的每个变量都存放在栈上:它的大小在编译期就已知,并且在作用域结束时自动销毁。这样既快又安全,但有一种情况它处理不了:直到程序运行起来,你才知道需要多少内存。比如大小由用户输入决定的缓冲区、必须比创建它的函数活得更久的数据结构,或者形状无法提前知道的图。

对于这些情况,C++ 允许你用 new 从堆(也叫自由存储区)上分配内存,再用 delete 把它还回去。new 表达式会预留一块内存,运行构造函数,然后返回指向它的指针,这正是建立在上一页讲过的指针之上。

变量 p 本身在栈上,它只是一个指针。它指向的那个 int 在堆上,不管经过多少个作用域的进出,都会一直存活,直到你 delete 它。

栈与堆

这个区别正是 new 存在的全部理由,所以值得把它讲具体。

void demo() {
    int a = 10;            // on the stack - gone when demo() returns
    int* b = new int(10);  // 'b' on the stack, the int it points to on the heap
}                          // 'a' destroyed; the heap int LEAKS - never deleted

关键区别:

  • 栈:生命周期自动管理,非常快,大小有限(通常只有几 MB),作用域退出时自动释放。
  • 堆:生命周期手动管理,稍慢一些,空间大,只有在你调用 delete 时才会释放。

这是用责任换灵活性:堆内存想活多久就活多久,但记得释放它的人变成了你自己。

用 new[] 分配数组

当你需要一块长度在运行时才确定的内存时,使用数组形式 new T[n]。它返回指向第一个元素的指针,你要用对应的 delete[] 来释放它。

这条规则很严格,也很容易弄错:new 得到的内存用 delete 释放,new[] 得到的内存用 delete[] 释放。把两者混用,比如对用 new[] 分配的东西执行 delete arr,就是未定义行为,即使它在你的机器上看起来能正常工作。

三个经典 bug

手动管理内存时,大多数堆相关的 bug 都出自少数几种错误。学会认出这三种。

1. 内存泄漏:你从没调用 delete。这块内存会永远被占着。发生一次无伤大雅,发生在循环里就是致命的。

void leaky() {
    int* p = new int(5);
    // ... no delete ...
}   // p is gone; the heap int is now unreachable AND unfreed

2. 悬空指针:你在释放内存之后还在使用它。指针里仍然保存着原来的地址,但那块内存已经不属于你了。

3. 重复释放:你对同一块内存 delete 了两次。这会破坏堆的内部记录,通常会导致崩溃。

int* p = new int(1);
delete p;
delete p;   // double-free - undefined behavior, often a crash

在 delete 之后把指针设为 nullptr,可以同时化解悬空使用和重复释放:解引用 nullptr 会立即崩溃(容易调试),而 delete nullptr 被明确规定为一个安全的空操作。

一个真实的“分配、使用、释放”流程

把这些放在一起,正确的手动管理是这样的:分配,使用,恰好释放一次,之后不再碰这个指针。

注意,对于类类型,delete u 做了两件事:先运行对象的析构函数,再释放原始内存。一旦你的对象自己也拥有资源,这个顺序就很重要了。

一个不易察觉的坑:如果在 new 和 delete 之间抛出了异常,delete 永远不会执行,内存就泄漏了。为了处理这种情况,给每次分配都包上 try/catch,既繁琐又容易出错,而这正是下一页要解决的问题。

下一步:智能指针

现在你已经看到了手动管理内存的全部代价:每个 new 都是一个日后要 delete 的承诺,而只要有一次漏掉、重复或过早释放,就是未定义行为。现代 C++ 几乎从不手动兑现这个承诺。下一页介绍智能指针 std::unique_ptr 和 std::shared_ptr:它们是拥有一块堆内存的对象,会在离开作用域时自动替你调用 delete,把这三个经典 bug 都交给编译器和 RAII 去处理。

常见问题

C++ 中 new 和 delete 有什么区别?

new 在运行时从堆上分配内存,并返回指向它的指针;delete 释放用 new 分配的内存。每个 new 都必须对应恰好一个 delete,否则就会泄漏内存。对于数组,用 new[] 配合 delete[]。

C++ 中忘记调用 delete 会怎样?

会造成内存泄漏:这块堆内存在程序的整个生命周期里都一直被占着,尽管已经没有任何东西指向它了。一次泄漏通常无伤大雅,但在循环里或长时间运行的服务中,泄漏会不断累积,直到程序耗尽内存而崩溃。

现代 C++ 中应该直接使用 new 和 delete 吗?

很少需要。优先使用 std::vector 这样的容器,或者能自动释放内存的智能指针(std::unique_ptr、std::shared_ptr)。裸的 new/delete 值得理解,因为智能指针就是对它们的包装,但在日常代码里,它们是内存泄漏和悬空指针的源头。

Coddy 编程语言插图

和 Coddy 一起学编程

开始