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_
相似文章
你的Rust服务没有内存泄漏——可能是分配器的问题
本文描述了一次调试过程:一个Rust服务在负载下内存持续居高不下,但并没有真正泄漏,原因是glibc的ptmalloc分配器没有将释放的内存归还给操作系统。文章解释了分配器的行为,并为Rust开发者提供了见解。
Dependable C
一个全面的资源,提供编写可靠、安全的C代码的指南和建议,涵盖未定义行为、内存模型以及特定版本的注意事项。
C字符串:一个50年的错误
对C语言中空终止字符串的批评,认为基于长度的字符串在现代编程中更优越,可以减少错误并提高性能。
C++中老式C风格void*的优雅与简洁
文章讨论了在C++中使用C风格void*指针与uint8_t*和std::span传递内存块的优缺点,主张void*的简洁性和可读性。
C++的创造者:"AI生成的代码尚未成熟——它会导致更多错误、更臃肿、更多安全漏洞,而且几乎无法验证"
Bjarne Stroustrup批评AI生成的代码,称其引入更多错误、臃肿和安全漏洞,且几乎无法验证,而资深开发者宁愿退休也不愿处理它。