Bjarne Stroustrup: 如何处理内存泄漏?
摘要
Bjarne Stroustrup 回答关于 C++ 内存泄漏的常见问题,并提供现代 C++ 内存管理技术的指导。
暂无内容
查看缓存全文
缓存时间:
2026/05/08 18:29
# C++ 风格与技巧 FAQ
来源: https://www.stroustrup.com/bs_faq2.html
首页(https://www.stroustrup.com/index.html) | C++ (https://www.stroustrup.com/C++.html) | FAQ (https://www.stroustrup.com/bs_faq.html) | 技术 FAQ (https://www.stroustrup.com/bs_faq2.html) | 出版物 (https://www.stroustrup.com/papers.html) | WG21 论文 (https://www.stroustrup.com/WG21.html) | TC++PL (https://www.stroustrup.com/4th.html) | Tour++ (https://www.stroustrup.com/tour3.html) | 编程 (https://www.stroustrup.com/programming.html) | D&E (https://www.stroustrup.com/dne.html) | 简历 (https://www.stroustrup.com/bio.html) | 访谈 (https://www.stroustrup.com/interviews.html) | 视频 (https://www.stroustrup.com/videos.html) | 名言 (https://www.stroustrup.com/quotes.html) | 应用 (https://www.stroustrup.com/applications.html) | 指南 (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) | 编译器 (https://www.stroustrup.com/compilers.html)
## Bjarne Stroustrup (https://www.stroustrup.com/index.html) 的 C++ 风格与技巧 FAQ
最后修改于 2022 年 2 月 26 日
这些是我经常被问到的关于 C++ 风格与技巧的问题。如果你有更好的问题或对答案有意见,欢迎给我发邮件(bs at cs dot tamu dot edu)。请记住,我不能把所有时间都花在改进主页上。我为新的统一 isocpp.org C++ FAQ (http://isocpp.org/faq) 做出了贡献,该 FAQ 由 C++ 基金会 (http://isocpp.org/about) 维护,我是其董事。本 FAQ 的维护可能会越来越不频繁。此外,C++ 核心指南 (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) 包含了一套维护良好的现代 C++ 使用指南。如需更一般的问题,请参阅我的 通用 FAQ (https://www.stroustrup.com/bs_faq.html)。关于术语和概念,请参阅我的 C++ 词汇表 (https://www.stroustrup.com/glossary.html)。
请注意,这些只是问题和答案的集合。它们不能替代一本好教科书中精心挑选的示例和解释序列,也不能提供像参考手册或标准那样详细精确的规范。有关 C++ 设计的问题,请参阅《C++ 的设计与演化》(https://www.stroustrup.com/dne.html)。有关 C++ 及其标准库的使用问题,请参阅《C++ 程序设计语言》(https://www.stroustrup.com/3rd.html)。
翻译:
- 中文 (https://www.stroustrup.com/bstechfaq.htm)(部分 Q&A 带注释)
- 另一个中文版本 (https://www.stroustrup.com/bsfaq2cn.html)
- 匈牙利语 (https://urldefense.com/v3/__https://pocket-guidebook.com/c-stilusu-es-technika-gyik.html__;!!KwNVnqRv!A78S6P9yITwrN1kDiBFUXNh6zk9ZFdmGrsLcdynii5s0cYEHlvc3GjydmCyW3-0W4Cig4_NHfJZNCD0Nfcg$)
- 日语 (http://www.libjingu.jp/trans/bs_faq2-j.html)
- 乌克兰语 (https://edu.clipart-library.com/bjarne-stroustrups-c-style.html)
- 俄语 (https://pngflare.com/ru-c-plus-plus)
- 主题:
- 入门 (https://www.stroustrup.com/bs_faq2.html#start)
- 类 (https://www.stroustrup.com/bs_faq2.html#classes)
- 层次结构 (https://www.stroustrup.com/bs_faq2.html#hierarchies)
- 模板与泛型编程 (https://www.stroustrup.com/bs_faq2.html#templates)
- 内存 (https://www.stroustrup.com/bs_faq2.html#memory)
- 异常 (https://www.stroustrup.com/bs_faq2.html#exceptions-i)
- 其他语言特性 (https://www.stroustrup.com/bs_faq2.html#other)
- 琐事与风格 (https://www.stroustrup.com/bs_faq2.html#trivia)
- 入门:
- 如何编写这个非常简单的程序?(https://www.stroustrup.com/bs_faq2.html#simple-program)
- 你能推荐一个编码标准吗?(https://www.stroustrup.com/bs_faq2.html#coding-standard)
- 如何从输入读取字符串?(https://www.stroustrup.com/bs_faq2.html#read-string)
- 如何将整数转换为字符串?(https://www.stroustrup.com/bs_faq2.html#int-to-string)
- 类:
- C++ 对象在内存中如何布局?(https://www.stroustrup.com/bs_faq2.html#layout-obj)
- 为什么“this”不是引用?(https://www.stroustrup.com/bs_faq2.html#this)
- 为什么空类的大小不为零?(https://www.stroustrup.com/bs_faq2.html#sizeof-empty)
- 如何定义类内常量?(https://www.stroustrup.com/bs_faq2.html#in-class)
- 为什么作用域结束时析构函数没有被调用?(https://www.stroustrup.com/bs_faq2.html#delete-scope)
- “friend”是否破坏了封装?(https://www.stroustrup.com/bs_faq2.html#friend)
- 为什么我的构造函数工作不正常?(https://www.stroustrup.com/bs_faq2.html#explicit-ctor)
- 类层次结构:
- 为什么编译花费这么长时间?(https://www.stroustrup.com/bs_faq2.html#abstract-class)
- 为什么我必须将数据放在类声明中?(https://www.stroustrup.com/bs_faq2.html#data-in-class)
- 为什么成员函数默认不是虚函数?(https://www.stroustrup.com/bs_faq2.html#virtual)
- 为什么没有虚构造函数?(https://www.stroustrup.com/bs_faq2.html#virtual-ctor)
- 为什么析构函数默认不是虚函数?(https://www.stroustrup.com/bs_faq2.html#virtual-dtor)
- 什么是纯虚函数?(https://www.stroustrup.com/bs_faq2.html#pure-virtual)
- 为什么 C++ 没有 final 关键字?(https://www.stroustrup.com/bs_faq2.html#final)
- 可以在构造函数中调用虚函数吗?(https://www.stroustrup.com/bs_faq2.html#vcall)
- 可以阻止别人从我的类派生吗?(https://www.stroustrup.com/bs_faq2.html#no-derivation)
- 为什么 C++ 没有通用的 Object 类?(https://www.stroustrup.com/bs_faq2.html#object)
- 我们真的需要多重继承吗?(https://www.stroustrup.com/bs_faq2.html#multiple)
- 为什么重载对派生类不起作用?(https://www.stroustrup.com/bs_faq2.html#overloadderived)
- 我能像 Java 那样使用“new”吗?(https://www.stroustrup.com/bs_faq2.html#new-java)
- 模板与泛型编程:
- 为什么我不能为模板参数定义约束?(https://www.stroustrup.com/bs_faq2.html#constraints)
- 为什么不能将一个 vector 赋值给另一个 vector?(https://www.stroustrup.com/bs_faq2.html#conversion)
- “泛型”是不是本应是模板的样子?(https://www.stroustrup.com/bs_faq2.html#generics)
- 既然有“老式的 qsort()”,为什么还要用 sort()?(https://www.stroustrup.com/bs_faq2.html#sort)
- 什么是函数对象?(https://www.stroustrup.com/bs_faq2.html#function-object)
- 什么是 auto_ptr,为什么没有 auto_array?(https://www.stroustrup.com/bs_faq2.html#auto_ptr)
- 为什么 C++ 不提供异构容器?(https://www.stroustrup.com/bs_faq2.html#containers)
- 为什么标准容器这么慢?(https://www.stroustrup.com/bs_faq2.html#slow-containers)
- 内存:
- 如何处理内存泄漏?(https://www.stroustrup.com/bs_faq2.html#memory-leaks)
- 为什么 C++ 没有类似于 realloc() 的功能?(https://www.stroustrup.com/bs_faq2.html#renew)
- **new** 和 **malloc()** 有什么区别?(https://www.stroustrup.com/bs_faq2.html#malloc)
- 能否混合使用 C 风格和 C++ 风格的分配与释放?(https://www.stroustrup.com/bs_faq2.html#realloc)
- 为什么必须使用强制转换来转换 void*?(https://www.stroustrup.com/bs_faq2.html#void-ptr)
- 有“placement delete”吗?(https://www.stroustrup.com/bs_faq2.html#placement-delete)
- 为什么 delete 不将其操作数置零?(https://www.stroustrup.com/bs_faq2.html#delete-zero)
- 数组有什么问题?(https://www.stroustrup.com/bs_faq2.html#arrays)
- 异常:
- 为什么使用异常?(https://www.stroustrup.com/bs_faq2.html#exceptions-why)
- 如何使用异常?(https://www.stroustrup.com/bs_faq2.html#exceptions)
- 为什么捕获异常后不能恢复执行?(https://www.stroustrup.com/bs_faq2.html#resume)
- 为什么 C++ 不提供“finally”构造?(https://www.stroustrup.com/bs_faq2.html#finally)
- 可以从构造函数中抛出异常吗?从析构函数中呢?(https://www.stroustrup.com/bs_faq2.html#ctor-exceptions)
- 不应当使用异常做什么?(https://www.stroustrup.com/bs_faq2.html#exceptions-what-not)
- 其他语言特性:
- 可以写“void main()”吗?(https://www.stroustrup.com/bs_faq2.html#void-main)
- 为什么不能重载 dot、::、sizeof 等?(https://www.stroustrup.com/bs_faq2.html#overload-dot)
- 可以定义自己的运算符吗?(https://www.stroustrup.com/bs_faq2.html#overload-operator)
- 如何在 C++ 中调用 C 函数?(https://www.stroustrup.com/bs_faq2.html#callC)
- 如何在 C 中调用 C++ 函数?(https://www.stroustrup.com/bs_faq2.html#callCpp)
- 为什么 C++ 既有指针又有引用?(https://www.stroustrup.com/bs_faq2.html#pointers-and-references)
- 应该用 NULL 还是 0?(https://www.stroustrup.com/bs_faq2.html#null)
- i++ + i++ 的值是多少?(https://www.stroustrup.com/bs_faq2.html#evaluation-order)
- 为什么 C++ 中有些东西是未定义的?(https://www.stroustrup.com/bs_faq2.html#undefined)
- static_cast 有什么好处?(https://www.stroustrup.com/bs_faq2.html#static-cast)
- 那么,使用宏有什么问题?(https://www.stroustrup.com/bs_faq2.html#macro)
- 琐事与风格:
- “cout”如何发音?(https://www.stroustrup.com/bs_faq2.html#cout)
- “char”如何发音?(https://www.stroustrup.com/bs_faq2.html#char)
- “int* p;”正确还是“int *p;”正确?(https://www.stroustrup.com/bs_faq2.html#whitespace)
- 哪种布局风格最适合我的代码?(https://www.stroustrup.com/bs_faq2.html#layout-style)
- 如何命名变量?你推荐“匈牙利命名法”吗?(https://www.stroustrup.com/bs_faq2.html#Hungarian)
- 应该用传值还是传引用?(https://www.stroustrup.com/bs_faq2.html#call-by-reference)
- 应该将“const”放在类型之前还是之后?(https://www.stroustrup.com/bs_faq2.html#constplacement)
---
## 如何编写这个非常简单的程序?
通常,尤其是在学期开始时,我会收到很多关于如何编写非常简单程序的问题。通常,要解决的问题是读取几个数字,对它们进行处理,然后输出答案。下面是一个示例程序:
``
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<double> v;
double d;
while(cin>>d) v.push_back(d); // 读取元素
if (!cin.eof()) { // 检查输入是否失败
cerr << "格式错误\n";
return 1; // 错误返回
}
cout << "读取了 " << v.size() << " 个元素\n";
reverse(v.begin(),v.end());
cout << "逆序元素:\n";
for (int i = 0; i < v.size(); ++i) cout << v[i] << '\n';
}
``
序列中的读书:
- 这个程序没有显式的内存管理,也不会泄漏内存。`vector` 管理其存储元素的内存。当 `vector` 需要更多元素内存时,它会分配更多;当 `vector` 超出作用域时,它会释放该内存。因此,用户无需关心 `vector` 元素内存的分配和释放。
- 关于读取字符串,请参阅 如何从输入读取字符串?(https://www.stroustrup.com/bs_faq2.html#read-string)。
- 程序在看到“文件结束”时停止读取输入。如果你在 Unix 机器上从键盘运行程序,“文件结束”是 Ctrl-D。如果你在由于 bug 而不识别文件结束字符的 Windows 机器上,你可能会更喜欢这个稍微复杂一点的程序版本,它用单词"end"终止输入:
``
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
vector<double> v;
double d;
while(cin>>d) v.push_back(d); // 读取元素
if (!cin.eof()) { // 检查输入是否失败
cin.clear(); // 清除错误状态
string s;
cin >> s; // 查找终止字符串
if (s != "end") {
cerr << "格式错误\n";
return 1; // 错误返回
}
}
cout << "读取了 " << v.size() << " 个元素\n";
reverse(v.begin(),v.end());
cout << "逆序元素:\n";
for (int i = 0; i < v.size(); ++i) cout << v[i] << '\n';
}
``
---
## 为什么我的编译花费这么长时间?
你很可能将实现细节包含在了接口中。例如:
``
class Some_class {
public:
void some_function();
// ...
private:
Some_class_impl* p; // 指向实现的指针
};
``
如示例所示,使用指向实现的指针(通常称为 "pimpl" 习惯用法)可以将编译时依赖性从接口定义转移到实现文件中,从而减少编译时间。当然,你也可以使用抽象类。
一个接口的典型例子是:
``
class Some_class {
public:
virtual void some_function() = 0;
virtual ~Some_class();
};
``
或者使用模板,这样就不需要在运行时使用虚函数。
注意:许多现代 C++ 编程技术(包括这些)在《C++ 程序设计语言》中有描述。另请参阅我的 C++ 词汇表 (https://www.stroustrup.com/glossary.html) 中的 "Handle"、"Interface" 和 "Pimpl" 条目。
---
## 为什么我必须将数据放在类声明中?
不必要。如果你不希望在接口中包含数据,就不要将它放在定义接口的类中,而是放在派生类中。参见 为什么我的编译花费这么长时间?(https://www.stroustrup.com/bs_faq2.html#abstract-class)。
有时,你确实希望在类中包含表示数据。考虑类 `complex`:
``
template<class Scalar>
class complex {
public:
complex() : re(0), im(0) { }
complex(Scalar r) : re(r), im(0) { }
complex(Scalar r, Scalar i) : re(r), im(i) { }
// ...
complex& operator+=(const complex& a) { re+=a.re; im+=a.im; return *this; }
// ...
private:
Scalar re, im;
};
``
这种类型的设计目的是像内置类型一样使用,并且声明中需要表示数据,以便能够创建真正的局部对象(即在栈上而非堆上分配的对象),并确保简单操作的内联。真正的局部对象和内联对于使 `complex` 的性能接近具有内置 `complex` 类型的语言是必要的。
---
## 为什么成员函数默认不是虚函数?
因为许多类并不是设计用作基类的。例如,参见 类 complex (https://www.stroustrup.com/bs_faq2.html#data-in-class)。此外,具有虚函数的类的对象需要虚函数调用机制所需的空间——通常是每个对象一个词。这个开销可能很大,并且会妨碍与其他语言(如 C 和 Fortran)数据的布局兼容性。有关更多设计原理,请参阅《C++ 的设计与演化》(https://www.stroustrup.com/dne.html)。
---
## 为什么析构函数默认不是虚函数?
因为许多类并不是设计用作基类的。虚函数只在那些旨在作为派生类对象的接口的类中才有意义(通常这些对象在堆上分配并通过指针或引用访问)。那么何时应该将析构函数声明为虚函数?只要类至少有一个虚函数。拥有虚函数表明该类旨在作为派生类的接口,此时派生类对象可能通过基类指针销毁。例如:
``
class Base {
// ...
virtual ~Base();
};
class Derived : public Base {
// ...
~Derived();
};
void f()
{
Base* p = new Derived;
delete p; // 使用虚析构函数确保 ~Derived 被调用
}
``
如果 `Base` 的析构函数不是虚函数,则 `Derived` 的析构函数不会被调用——很可能导致不良后果,例如 `Derived` 拥有的资源不会被释放。
---
## 为什么没有虚构造函数?
虚调用是一种基于部分信息完成工作的机制。特别是,“虚”允许我们在只知道接口而不知道对象确切类型的情况下调用函数。要创建对象,你需要完整的信息。特别是,你需要知道要创建的对象的精确类型。因此,“对构造函数的调用”不能是虚的。
在要求创建对象时使用间接的技术通常被称为“虚构造函数”。例如,参见 TC++PL3 15.6.2。下面是一种使用抽象类生成适当类型对象的技术:
``
struct F { // 对象创建函数的接口
virtual A* make_an_A() const = 0;
virtual B* make_a_B() const = 0;
};
void user(const F& fac)
{
A* p = fac.make_an_
相似文章
arXiv cs.CL
StageMem 提出了一种面向语言模型的生命周期管理记忆框架,该框架将记忆划分为瞬态、工作状态和持久状态三个阶段,并引入明确的置信度与强度指标,将记忆视为一种有状态的处理流程而非静态存储,从而在容量受限的条件下更精准地管理信息的保留与遗忘。
Lobsters Hottest
Matt Godbolt 探讨了编译器优化如何将 O(n) 求和循环转换为 O(1) 的闭式解,突出了 Clang 和 GCC 如何采用循环展开和数学简化等复杂技术来大幅提升代码性能。
Lobsters Hottest
TokioConf 2026 的一篇演讲/博客文章探讨了如何通过为复杂指针结构实现追踪式垃圾回收,将安全 Rust 推向极限,并分享处理循环引用与原始指针 GC 设计的技巧。
Hugging Face Daily Papers
研究者推出BEHEMOTH基准与CluE聚类提示优化,使LLM能从多样化任务中抽取并保留异构记忆,相比既往自演化框架提升9%。
Hacker News Top
# Rust 零拷贝页面:我是如何停止焦虑并爱上生命周期的
来源:[https://redixhumayun.github.io/databases/2026/04/14/zero-copy-pages-in-rust.html](https://redixhumayun.github.io/databases/2026/04/14/zero-copy-pages-in-rust.html)
*你可以在[这里](https://github.com/redixhumayun/simpledb/)找到该项目的源代码*
零拷贝是一种旨在消除内核与用户空间缓冲区之间 CPU 数据复制的技术,尤其在数据处理等高吞吐量应用中极具价值。