C++中的内存泄漏

45

我刚刚写了一段 C++ 代码,用于字符串操作,但是当我使用 valgrind 进行检查时,发现可能存在内存泄漏。我对代码进行了深度调试,并写了一个类似这样的简单 C++ 程序:

#include<iostream>
#include<cstdlib>
using namespace std;
int main()
{
        std::string myname("Is there any leaks");
        exit(0);
}

并且在运行valgrind时我得到了:

==20943== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 26 from 1)
==20943== malloc/free: in use at exit: 360,645 bytes in 12,854 blocks.
==20943== malloc/free: 65,451 allocs, 52,597 frees, 2,186,968 bytes allocated.
==20943== For counts of detected errors, rerun with: -v
==20943== searching for pointers to 12,854 not-freed blocks.
==20943== checked 424,628 bytes.
==20943== 
==20943== LEAK SUMMARY:
==20943==    definitely lost: 0 bytes in 0 blocks.
==20943==      possibly lost: 917 bytes in 6 blocks.
==20943==    still reachable: 359,728 bytes in 12,848 blocks.
==20943==         suppressed: 0 bytes in 0 blocks.
==20943== Reachable blocks (those to which a pointer was found) are not shown.
==20943== To see them, rerun with: --show-reachable=yes

然后我想到我们已经强制退出了程序(这也是我在原始的C++代码中执行的操作)。现在问题是我想要退出程序,因为我的旧代码等待新代码的退出状态。例如,二进制文件a.out 等待 b.out 的退出状态。有没有办法避免内存泄漏,或者我应该真的担心内存泄漏,因为程序已经在那一点上退出。

这也让我产生了另一个问题,这样的代码是否有害?

#include<stdio.h>
#include<cstdlib>
int main()
{
        char *p=(char *)malloc(sizeof(char)*1000);
        exit(0);
}

56
在 main() 函数中返回一个值,将使用该值作为进程的退出代码。 - tinman
2
请注意,正如valgrind正确指出的那样,在这种情况下您没有任何泄漏的资源。在进程生命周期结束时,仍然可以访问一些分配的内存字节(您仍然拥有指向它们的指针),但这不是“泄漏”。 - Frerich Raabe
10
相信您已经读到了,"return exitcode;"比"exit(exitcode);"更好,因为它们在功能上都设置了退出代码状态。“return”在清理方面做得更好,而“exit”很可能会绕过清理。 - Stephen Quan
除了valgrind,你尝试过其他工具吗?比如deleaker... - MastAvalons
请注意,当您从 main() 返回一个值时,它实际上不是一个 int。它会被转换为有效的退出代码范围,即 [0,256)(至少对于 Posix)。 - moooeeeep
你可以使用这个工具来查找内存泄漏:http://theunixshell.blogspot.com/2013/11/finding-memory-leaks-on-solaris-is-no.html - Vijay
10个回答

65

main 函数的结尾,使用 return 0; 替代 exit(0);。使用 exit 绕过了析构函数的执行。


正如我之前提到的,我必须使用exit,因为还有另一个程序正在等待此代码的退出状态。由于程序a.out正在分叉运行b.out,就像在整个项目中使用C++代码来调用C代码一样。 - Global Warrior
38
以返回12为例,它应该像使用exit(12)一样向操作系统返回相同的退出状态,只不过它是干净地退出。 - jcoder
10
为了澄清一下,@Piyush - 不,你不需要调用exit。正如JohnB所说,从主函数返回的整数被用作退出状态 - 这就是它存在的目的。 - Useless

63

如果你坚持使用exit():

#include<iostream>
int main(){
    {
        std::string myname("Are there any leaks?");
    }
    exit(0);
}

此外,当你从main函数返回时,返回值将成为应用程序的退出码。因此,如果您想传递一个退出码,请在main()中使用return exitCode;而不是exit

对于这部分内容:

这也引发了我另一个问题,这样的代码是否有害?

是的,因为这是一种糟糕的编程习惯。

操作系统将清理您未能释放的任何内存,只要您没有耗尽所有系统内存和页面文件,就不会损坏操作系统。

但是,编写松散/泄漏的代码可能会成为一种习惯,因此依靠操作系统清理您的混乱是一个坏主意。


14
操作系统可能会清理内存,但并不能完全保证这样做(尽管大多数情况下它会这样做)。 - TC1
4
只有糟糕的操作系统才不会这样做。代码通常都是糟糕的,除非有证明表明它是好的。 - whitequark
72
专业提示:假设您正在运行死亡站点9000上,这是一台主要从小猫咪的大脑中分配内存的机器,只有在明确告知的情况下才会将内存放回。此外,任何未定义行为的出现都将导致地球的毁灭。 - Kaz Dragon
5
@SigTerm 这不是为了省掉三行代码。我感谢你的建议适用于初学者程序员,但是在某个阶段,你可以做得更好。为什么Firefox需要30秒才能关闭?因为它会很注重清理操作系统可以在毫秒内丢弃的内容,并且当前的编程语言不能区分可选资源释放和必要的最终处理(例如刷新缓冲区)。你建议我们放弃并清理所有东西。我建议我们尽可能地努力。 - Roman Starkov
3
对于不想争论的人来说,你似乎在大量争论 :) - Timwi
显示剩余9条评论

22

这也引发了我另一个问题,这样的代码是否有害?

#include<stdio.h>
int main()
{
        char *p=(char *)malloc(sizeof(char)*1000);
        exit(0);
}

在现代操作系统上,这不会造成伤害,因为当进程结束时,它们会自动关闭所有资源。

然而,这仍然是一种不好的做法,可能会在多年维护后引起微妙且难以发现的错误,直到某一天,它才真正变成有害的代码。 我曾经参与了一些代码已经十年之久的项目,并从中学到了一些经验教训,其中一些相当严厉。 因此,我建议避免编写这样的代码,即使目前看起来并没有问题。


27
但这可能会对您代码的维护者造成伤害,因为他们将更难使用诸如valgrind之类的工具来追踪合法问题。 - John Zwinck
也许这就是为什么应该改进这些工具的论据。我的意思是,它们目前正在阻止一种合法的技术:不花时间去做操作系统可以更好地完成的事情。 - Roman Starkov
如果您想在C或C++程序中编写一个"fastTeardownWithoutMemoryDeallocation()"函数。当且仅当它被调用时,忘记所有的free()和delete()调用并终止程序。但是编写不会在析构函数中清理的类是不好的,因为您不知道哪些类在运行时是临时使用的,哪些是在程序关闭之前一直存在的。 - John Zwinck

5
在大多数情况下,为了许多好的原因(如更好的可维护性、更好的检查工具等),自己清理代码是值得的。
如果有其他功能性的理由需要清理,比如你的数据被保存到持久存储器中,那么你就没有选择——你必须清理(尽管你可能需要重新考虑你的设计)。
然而,在某些情况下,最好只是退出并“泄漏”。
在程序结束时,进程将退出。当它这样做时,操作系统将恢复程序分配的任何内存,并且在某些情况下可以更快地完成此操作。
考虑一个大型链接列表,其中每个节点都是动态分配的,并携带一个实质性的动态分配结构。要清理这个结构,您必须访问每个节点并释放每个有效负载(这反过来可能会导致其他复杂结构被遍历)。
你可能最终执行数百万次内存操作来运行这样的结构。
用户想要退出你的程序,他们坐在那里等待几十秒钟的垃圾处理。他们不可能对结果感兴趣——毕竟他们正在退出程序。
如果你让这个信息“泄漏”出去,操作系统就可以更快地回收分配给进程的整个内存块。它不关心结构和任何对象清理。

http://blogs.msdn.com/b/oldnewthing/archive/2012/01/05/10253268.aspx

最终你必须理解你的工具在告诉你什么,以确保你正确地使用它们。


1
编写代码来清理程序终止时动态分配的内存,就像在函数体中额外加上一对“{}”来“确保”自动变量在函数返回时被销毁一样愚蠢。 - user168715

4
为避免内存泄漏,请从main返回状态,而不是调用exit。如果您返回零,则可以省略return语句;在这种情况下,程序将以零的状态退出。

这也引发了我另一个问题,这样的代码是否有害?

在现代操作系统上,它不会造成任何伤害-当程序终止时,所有资源都会自动回收。然而,它确实使使用像Valgrind这样的工具来查找真正的问题变得更加困难,因此最好尽可能避免即使无害的内存泄漏。

2
#include<iostream>
using namespace std;
int main()
{
    {
        std::string myname("Is there any leaks");
    }
    exit(0);
}

1

当您的进程实际退出时,例如在main()函数退出时,操作系统将无论如何回收分配给应用程序的所有资源。您如何退出并不是很重要,至少就动态内存而言是如此。

如果您有一些分布式数据库连接打开或其他类似情况,您应该使用atexit()处理程序来关闭它们,直接使用exit()强制终止可能会导致它们无法运行,这将是不好的,但就操作系统资源而言,您可能还好。

您还应始终确保释放(手动)文件锁定和类似的东西,因为它们可能由于进程退出而不会消失。


1
如果您想在不超过析构函数的情况下中断执行并传递返回代码,请抛出异常,并从 main() 中提取异常的返回值。

你为什么要这样做而不是调用std :: exit()呢?那么,将来三年后会出现什么情况呢?维护程序员会放置一个catch(...)吗?因为他认为除了他的13个异常类型(他太懒了,没有组织)之外,不会有其他情况发生在那个子模块中? - sbi

0

提出不同的观点。

这样的代码并不会对系统造成任何伤害。当进程终止时,操作系统会处理所有事情。其他一切都会导致不稳定的操作系统。只需确保您的持久数据(文件等)是一致的即可。

更进一步地说,明确在程序退出时释放内存可能会有害。

  1. 程序退出需要更长时间(你是否曾经因为等待程序退出而感到烦恼,直到计算机关闭?)
  2. 正确的销毁顺序并不总是显而易见,特别是对于第三方组件(我记得有些程序可能在退出时崩溃)
  3. 离开 main(*) 后,操作系统可能不允许您释放内存,而是杀死您的程序

您冒这个风险只是为了让 Valgrind 给您一个特定的输出吗?(**)


(*)

#include<iostream>
using namespace std;
std::string myname("Is there any leaks");
int main() {
        exit(0);
}

当然,任何内存分析器的输出结果去除“噪音”后更有用。在调试模式下只在退出时显式释放内存如何?


0
如果程序正在退出,您不必担心使用mallocnew分配的内存。操作系统会处理它 - 当进程死亡时,进程独占的虚拟地址空间中的任何内容都将消失。如果您正在使用共享内存或命名管道,则仍可能存在问题。

3
但是必须记住,exit 不会调用在栈上分配的对象的析构函数(因此例如文件缓冲区可能不会被刷新),因此最好还是避免使用它。 - Matteo Italia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接