以下代码是不是容易出现内存泄漏?
Posted
技术标签:
【中文标题】以下代码是不是容易出现内存泄漏?【英文标题】:Whether following code is prone to memory leak?以下代码是否容易出现内存泄漏? 【发布时间】:2011-12-22 22:47:09 【问题描述】:我是 C++ 新手,我想知道以下代码是否容易出现内存泄漏。
在这里,我使用std::ostream
指针将输出重定向到控制台或文件。
为此,我为std::ofstream
调用新操作员。
#include <iostream>
#include <fstream>
int main()
bool bDump;
std::cout << "bDump bool" << std::endl;
std::cin >> bDump;
std::ostream *osPtr;
if (bDump)
osPtr = new std::ofstream("dump.txt");
else
osPtr = &std::cout;
*osPtr << "hello";
return 0;
还有一件事,我没有关闭在为ofstream
调用构造函数时打开的文件。我们这里是否有任何潜在的数据丢失情况。因为文件没有关闭。
【问题讨论】:
内存泄漏不会发生在主函数中,除非你有类似 for/while 的东西。 @Radu:这取决于你对内存泄漏的定义,不是吗? 我相信这在技术上是未定义的行为:3.8 [basic.life]/4 “对于具有非平凡析构函数的类类型的对象,......如果不使用删除表达式为了释放存储,不应隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序都有未定义的行为。”因此,按照标准的规定,当main
结束时,如果bDump
是true
,则该程序可能会向操作系统请求一些共享内存,然后突然结束,使其永远不会被回收,从而导致实际的永久内存泄漏。
@DennisZickefoose:如果它依赖于析构函数产生的副作用,那么它具有未定义的行为是正确的。但事实并非如此。如果析构函数向操作系统释放了一些这样的资源,那么它就会发生内存泄漏是正确的。但事实并非如此。 (所以,不,它没有内存泄漏。不,它没有未定义的行为。是的,这种类型的代码容易发生内存泄漏。)
@David:假设bdump
是true
:如果调用析构函数,文件“dump.txt”将包含文本“hello”。如果不调用析构函数,文件“dump.txt”将不包含文本“hello”。我同意你的观点,一般来说,像这样的泄漏在现代系统上不是问题。但只有如果有问题的对象有一个微不足道的析构函数...ofstream
不符合条件。
【参考方案1】:
是的。确实。任何时候你在没有delete
的情况下调用new
,都会发生内存泄漏。
在你的代码执行之后,你需要添加这个:
if(bDump)
delete osPtr;
【讨论】:
@lvella: 而在main
的最后,内存已经不可恢复地丢失了。
你肯定想在这里使用智能指针。如果发生异常,您希望析构函数正确关闭流。
@lvella:很明显,输给了程序。您如何建议在没有 osPtr
浮动的情况下刷新任何剩余的输出?
如果你不调用析构函数,那么我会认为资源泄露了。如果该物体是与空间站的链接,会发生什么。我敢肯定,如果你不彻底关闭连接,NASA 会非常生气。
@David Schwartz:我很好奇你如何调用泄露对象的析构函数。尽管问题在 C++ 中可能不同,但它们与我们处理的 objects 而非普通内存(对象是一种可能泄漏的资源)相同。任何泄漏的对象(如本例中)都不会调用其析构函数。因此资源被泄露。【参考方案2】:
正如@Mahmoud Al-Qudsi 提到的,您的任何新内容也必须删除,否则将被泄露。
在大多数情况下,您确实不想要使用删除,而是想要使用智能指针来自动删除对象。这是因为在出现异常的情况下,您可能会再次泄漏内存(在 RAII 时),智能指针将保证对象被删除,从而调用析构函数。
调用析构函数的 at 很重要(尤其是在这种情况下)。如果您不调用析构函数,则有可能不是流中的所有内容都将刷新到基础文件。
#include <iostream>
#include <fstream>
int doStuff()
try
bool bDump;
std::cout<<"bDump bool"<<std::endl;
std::cin>>bDump;
// Smart pointer to store any dynamic object.
std::auto_ptr<std::ofstream> osPtr;
if(bDump)
// If needed create a file stream
osPtr.reset(new std::ofstream("dump.txt"));
// Create a reference to the correct stream.
std::ostream& log = bDump ? *osPtr : std::cout;
log << "hello";
catch(...) throw;
// Smart pointer will correctly delete the fstream if it exists.
// This makes sure the destructor is called.
// This is guaranteed even if exceptions are used.
【讨论】:
【参考方案3】:是的,new
ed 但绝不会泄露delete
ed 的任何内容。
在某些情况下,分配左右和中心,然后退出是完全合理的,特别是对于短期批处理式程序,但作为一般规则,您应该 delete
new
和 @ 987654325@你的一切new[]
。
特别是在上述情况下,泄漏对象是不安全的,因为被泄漏的对象是一个永远不会写入未刷新内容的 ostream。
【讨论】:
我真的,真的不建议告诉人们分配而不是删除是可以的。当您真正需要它时、如果且仅当您真正需要它时,您会自己发现它。 @MahmoudAl-Qudsi:我同意这是一种应谨慎使用的危险做法,但我不同意将开发人员与现实隔离开来,尤其是当它可以让他们深入了解机器时在面纱后面。 我完全明白你在说什么-我只是觉得某些做法和方法最好由自己发现。【参考方案4】:显示的代码没有泄漏。在执行期间的任何时候,所有分配的对象都是可引用的。如果一个对象已经被分配并且不能以任何方式被引用,那么只有一个内存泄漏。
如果指针超出范围,或者它的值被改变,而对象没有被释放,那就是内存泄漏。但是只要指针在最外层范围内并且没有其他任何东西改变它的值,就没有泄漏。
“在面向对象的编程中,当对象存储在内存中但运行代码无法访问时,就会发生内存泄漏。” Wikipedia -- 'Memory leak'
其他答案表明,任何使用典型单例模式或在终止之前未释放所有已分配对象的程序都存在内存泄漏。国际海事组织,这很愚蠢。无论如何,如果您接受这个定义,那么几乎每个现实世界的程序或库都会发生内存泄漏,而且内存泄漏肯定不全是坏事。
从某种意义上说,这种编码容易导致内存泄漏,因为很容易改变指针的值或让它超出范围。在这种情况下,存在实际泄漏。但如图所示,没有泄漏。
你可能有不同的问题:如果析构函数有副作用,不调用它会导致错误的操作。例如,如果您从未在写入文件的缓冲输出流上调用析构函数,则最后一次写入可能永远不会真正发生,因为缓冲区没有被刷新到文件中。
【讨论】:
在终止之前不释放所有对象是内存泄漏,只有一个最现代的操作系统为您清理。实际上,这是示例代码中发生的内存泄漏。在许多情况下,当模块未映射并且不一定泄漏时,系统会破坏单例和其他对象。这个答案大多是不正确的。 事实上,由于osPtr
超出范围而没有释放对象,因此您对内存泄漏的定义与正在发生的情况相符。
一开始我也认为答案不正确,但现在我认为它是正确的。从技术上讲,有泄漏,但实际上 - 没有。 osPtr
的生命周期与程序的生命周期相匹配,因此不会发生实际泄漏。但是,我仍然认为这是一种糟糕的编码风格。例如,大多数内存分析器会将其报告为泄漏。
@Alex 这肯定是不好的风格。但是,存在泄漏,指针超出范围,内存丢失并且永远不会被程序释放。这只是一个短暂的泄漏,因为操作系统会为您进行清理。仍然是一个非常真实的泄漏,任何其他函数中的类似代码都可能导致严重的问题。
这是真正泄漏的完美案例。最后 fstream 指针丢失。如果它没有被删除,那么流就不会关闭。如果流未关闭,则可能不会将数据刷新到文件中。内存泄漏实际上与内存无关它与资源没有通过析构函数正确销毁有关。内存只是资源的最简单示例,但原则适用于内存中分配的所有对象。以上是关于以下代码是不是容易出现内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章