Bjarne Stroustrup: 如何处理内存泄漏?

Hacker News Top 工具

摘要

Bjarne Stroustrup 回答关于 C++ 内存泄漏的常见问题,并提供现代 C++ 内存管理技术的指导。

暂无内容
查看原文 导出为 Word 导出为 PDF
查看缓存全文

缓存时间: 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_

相似文章

StageMem:面向语言模型的生命周期管理记忆框架

arXiv cs.CL

StageMem 提出了一种面向语言模型的生命周期管理记忆框架,该框架将记忆划分为瞬态、工作状态和持久状态三个阶段,并引入明确的置信度与强度指标,将记忆视为一种有状态的处理流程而非静态存储,从而在容量受限的条件下更精准地管理信息的保留与遗忘。

当编译器让你惊喜

Lobsters Hottest

Matt Godbolt 探讨了编译器优化如何将 O(n) 求和循环转换为 O(1) 的闭式解,突出了 Clang 和 GCC 如何采用循环展开和数学简化等复杂技术来大幅提升代码性能。

安全 Rust 的边界

Lobsters Hottest

TokioConf 2026 的一篇演讲/博客文章探讨了如何通过为复杂指针结构实现追踪式垃圾回收,将安全 Rust 推向极限,并分享处理循环引用与原始指针 GC 设计的技巧。

跨异构任务的自演化LLM记忆抽取

Hugging Face Daily Papers

研究者推出BEHEMOTH基准与CluE聚类提示优化,使LLM能从多样化任务中抽取并保留异构记忆,相比既往自演化框架提升9%。

Rust 零拷贝页面:我是如何停止焦虑并爱上生命周期的

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 数据复制的技术,尤其在数据处理等高吞吐量应用中极具价值。