为什么要在运行时申请内存
到目前为止,你创建的每个变量都存放在栈上:它的大小在编译期就已知,并且在作用域结束时自动销毁。这样既快又安全,但有一种情况它处理不了:直到程序运行起来,你才知道需要多少内存。比如大小由用户输入决定的缓冲区、必须比创建它的函数活得更久的数据结构,或者形状无法提前知道的图。
对于这些情况,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 值得理解,因为智能指针就是对它们的包装,但在日常代码里,它们是内存泄漏和悬空指针的源头。