繁体   English   中英

C、C++ 中的内存泄漏; 忘了做免费,删除

[英]Memory leak in C,C++; forgot to do free,delete

我们在 C 中使用 malloc 分配内存,在 C++ 中使用 new 分配内存。 我知道必须使用 C 中的 free 和 C++ 中的 delete 释放分配的内存或将其返回给操作系统。 如果我在分配内存后忘记使用 free/delete,则意味着会有内存泄漏。

现在,我的问题是,这种内存泄漏是否仅在程序执行期间? 或者它是永久性泄漏/丢失还是在我重新启动系统后再次获得? 内部流程究竟是怎样的? 内存泄漏/丢失究竟是什么意思?

如果有人能详细解释这一点或为我提供一些不错的参考资料,我将不胜感激。

更新 1

看了一些答案,我了解到程序终止后内存会归还给操作系统/系统,如果是这样,为什么每个人都需要如此关心内存泄漏,为什么防止内存泄漏很重要?

更新 2

因此,应该防止内存泄漏,以便系统不会因分配内存不足而崩溃??

更新 3

因此,在阅读所有答案后,我意识到内存泄漏是防止系统崩溃的重要问题。 但是,对于像我这样的初学者,我如何确定我的程序是否完全没有内存泄漏。 我尝试免费,如果我使用 malloc,则删除,新但有时,它会变得混乱。 有什么工具或方法可以让我知道我的程序是否有内存泄漏?

更新 4

阅读答案后,我现在了解了无内存泄漏代码的重要性,更少使用 new/delete,更多使用 STL,学习了 RAII、valgrind 等新东西和良好的编程实践。 谢谢大家 :)

这是每个进程 一旦您的进程退出,分配的内存将返回给操作系统供其他进程(新的或现有的)使用。

要回答您编辑的问题,您的机器中只有有限的内存量。 因此,如果您有内存泄漏,那么主要问题是该内存不可用于其他进程。 次要但不可忽略的影响是您的过程映像增长,您将切换到磁盘并且性能将受到影响。 最后,您的程序将耗尽系统中的所有内存并失败,因为它无法为自己分配任何内存。

可以说,对于一个生命周期短的小进程来说,内存泄漏是可以容忍的,因为泄漏的内存数量少,生命周期短。

看看这个资源,可能比你需要的更多信息。 我们在这里讨论的是动态分配或分配。

内存泄漏只是意味着您的应用程序无法释放它已分配的内存。 程序结束后,由操作系统决定会发生什么。 每个现代操作系统都会回收应用程序使用的所有内存,因此一旦您的进程终止,它将被清理。

但是 C/C++ 不保证操作系统会这样做。 在某些平台上,内存可能会一直丢失,直到您重新启动。

所以内存泄漏的问题是双重的:

  • 系统可能必须重新启动才能回收内存的少数平台之一。 在大多数平台上,这不是问题,尽管泄漏一些其他资源类型可能仍然会导致问题。
  • 只要您的程序在运行,它就会分配永远不会释放的内存,这意味着它将使用越来越多的内存。 如果您的程序打算长时间运行,它最终可能会使用机器上的所有可用内存,并随后崩溃。

许多短时间运行的程序实际上会忽略内存泄漏,因为它们知道操作系统很快就会清除它。 据我所知,微软的 C++ 编译器就是这样做的。 他们知道,一旦调用编译器,它最多会运行几分钟。 (他们知道它运行在Windows,其中OS确实回收存储过程一旦终止的),所以没关系,它在这里和泄漏一些内存出现。

至于如何避免内存泄漏,就不要创建了。

每次使用 new/delete 时都有泄漏内存的风险,所以不要.

当您需要一组数据时,请执行以下操作:

std::vector<char> vec(200);

而不是这个:

char* arr = new char[200];

前者同样有效,但您不必显式调用 delete 来释放它std::vector使用 RAII 在内部管理其资源。 你也应该这样做——要么使用现成的 RAII 类,如vectorshared_ptr ,或者标准库或 Boost 中的几乎任何其他类,或者通过编写自己的类。

作为一般经验法则,您的代码不应包含任何 new/delete 调用,除了负责管理该分配的类的构造函数/析构函数。

如果一个对象在构造函数中分配了它需要的内存,并在析构函数中释放它(并正确处理复制/赋值),那么你可以在需要的时候简单地在堆栈上创建一个类的本地实例,它不会,不能,内存泄漏。

在 C++ 中不泄漏内存的关键是不调用 new/delete。

操作系统将跟踪内存,一旦您的程序终止将回收所有内存。 这只是意味着您的应用程序丢失了某些已分配内存的跟踪。

请注意,这可能不适用于某些操作系统,但适用于任何 windows/unix/mac 类型系统

Re: 检测内存泄漏的工具

如果您使用基于 Linux 的操作系统进行开发,您可以尝试使用 valgrind ( http://valgrind.org/ ) 来检测内存泄漏。

valgrind --leak-check=full ./compiled_binary

如果您的程序是用调试符号编译的(例如,对于 gcc,包括 -g 标志),valgrind 还会通知您分配泄漏内存的确切代码行。 这将大大简化跟踪和修复泄漏的任务。

优点:它是免费的

缺点:AFAIK,它只适用于 Linux

更新

正如在http://valgrind.org/info/platforms.html 上看到的,valgrind 正在被移植到其他操作系统(和平台),包括 MacOSX、FreeBSD 和 NetBSD。

更新 2

(有点跑题,但是……)

使用 valgrind 的好处是它不仅仅检查内存泄漏。 http://valgrind.org/info/tools.html

我将 buildbot 配置为对我所有的夜间构建运行 valgrind(和夹板),这已被证明是无价的!

有一些工具可以检测内存泄漏,例如Purify

作为 C++ 程序员的新手,我能给你的最好建议是学习如何最大限度地减少你编写的“new”和“delete”语句的数量。 如果您让编译器在堆栈上本地创建对象,它将为您管理内存,并在对象超出范围时自动删除它们。

有一种编程思想叫做资源获取即初始化( RAII )。 这意味着“如果您需要分配需要删除的内存,或者确保您打开的东西被关闭,请将其包装在您在堆栈上创建的对象中。这样当对象超出析构函数的范围时自动被调用,然后您在析构函数中删除您的资源。”

当您在代码中编写“new”时会发生常见的内存泄漏,但您的函数在调用 delete 之前退出。 有时您会过早地遇到“return”语句,有时会在“delete”语句之后抛出并捕获异常。 遵循 RAII 可帮助您确保不会发生这些事故。

这是内存泄漏。

基本上这意味着在进程被销毁之前不会回收这些内存。

问题是当指针超出范围并且您不释放内存时,它由进程分配,但程序无法知道它超出范围并且不再需要(不使用类似的工具)瓦尔格林)。

如果它重复发生,这只是一个主要问题。 如果是这样,那么程序在最终崩溃之前运行的时间越长,它就会继续使用越来越多的内存。 用户需要定期重新启动应用程序以避免这种情况发生或使用过多的系统资源。

更新 1:
如果您有一个简单的应用程序,它运行一次,执行它的操作并终止,那么内存泄漏就不是那么重要了。 这仍然是一种非常糟糕的做法,如果您的编码风格完全允许代码泄漏,那么您可能会将相同的泄漏放入重要的应用程序中 - 那些可以工作数天、数周或数年的应用程序。 我们这里有一个应用程序会泄漏,所以我们每个月都会重新启动它。 它不是一个理想的情况。

更新 2:
是的,差不多。 但是应该防止内存泄漏仅仅因为它们是一个错误,并且您永远不应该以错误是可以接受的观点编写代码。

更新 3:
防止内存泄漏的最佳方法是首先不要使用 malloc/free。 如果您在 RAII 上使用 C++ 阅读,使用类并复制对象,这将确保您永远不会有泄漏......几乎所有时间。 如果确实必须显式分配内存,请确保对其进行跟踪。 如果这意味着您需要一个全局对象来存储指针,那么就这样做。 如果这意味着您有一个存储指针的集合类,则获取一个。 永远不要将内存分配给您可能会忘记的局部变量,可能会从函数返回而不通过 free 调用,可能会传递给另一个没有被调用的函数以释放。 为此需要一种纪律感(不需要太多纪律),但很多人会告诉你,无论如何编写好的、正确的、设计良好的、无错误的代码也需要同样的美德(他们是对的 -如果您曾经见过与精心设计的代码相比的黑客代码,您将能够立即看到差异)。

笔记:
即使使用垃圾收集语言,您仍然会遇到内存泄漏。 人们将对象添加到集合中,然后忘记删除它们,因此对象永远保留在内存中。 这算作泄漏。 在 GC 语言中这样做是很常见的,因为人们认为 GC 会为他们做所有的工作。 同样,编码/设计纪律是必需的——知道你在做什么可以防止这些错误。

即使不使用 malloc/new,也可能发生内存泄漏。 注意覆盖已经指向某些内存的指针变量。 我有史以来最大的泄漏源是使用 MSXML,我创建了一个 XML 对象的 CComPtr,然后调用该方法来获取一个元素,将该对象作为参数传递给该方法。 不幸的是,该类被强制转换为内部指针,该方法只会用新指针覆盖它,从而导致旧数据泄露。 这里的寓意是,如果使用智能指针类,请确保您知道它在做什么,尤其是它的强制转换运算符。

工具:
如果在 Windows 上运行,则无需购买 Purify。 Microsoft 提供了UMDH ,它可以对您的内存进行快照。 拍摄 2 个快照并使用该工具进行比较,您可以看到分配随着时间的推移稳步增加而不会被取消分配。 它不漂亮,但它有效。

补充几点:

  1. 从一开始就学会正确工作——释放内存,改掉坏习惯很难。
  2. 内存不是应该使用 new/delete 管理或管理的唯一资源。

    例如,某个对象可能保存了一些应该在最后删除的临时文件。 通常这样的东西被绑定到某个对象,所以,如果你忘记删除对象,你忘记取消链接文件......即使系统重新启动,该资源也不会回来。

    还有许多其他类似的资源绑定到对象并使用 new/delete 进行管理:文件、套接字、共享内存、数据库连接等。 因此,您学习管理内存的所有技术都将帮助您管理您必须使用的其他非常有限的资源。

内存不会丢失,但它会保持分配状态,因此不可用于您的程序进行的下一次分配。 这意味着如果你的程序继续分配内存而不释放它,它会消耗越来越多的内存。 一段时间后,剩下未分配的内存,下一次分配新内存的尝试将失败,因此您的程序也会失败。

该内存取自所谓的“堆”。 这是您的程序本地的,并在您的程序完成时被完全删除。 因此,您的程序对系统和操作系统中运行的其他程序可能造成的“唯一”伤害是它们也可能无法分配内存,因为您的程序已经“吃光”了所有程序。 一旦你终止你的程序,其他人应该正常运行,如果他们没有因为分配问题在此期间崩溃。

监控内存泄漏的一个工具是覆盖 new 和 delete 操作符。 这允许您维护已分配和未释放的内存列表。 因此,如果某个特定对象应该释放它正在使用的所有内存,则此机制为您提供了一种验证它是否真的释放了内存的方法。

除了 Purify,您还可以尝试一个免费的替代方案:valgrind。 那里的规定是 valgrind 是特定于 linux 的解决方案。

要回答更新 3,通常有一种方法可以指示您的进程是否有任何未完成的内存分配 _heapwalk(在 Win32 下)将允许您逐步完成所有分配,您可以查看是否有任何未完成的分配。

除此之外,它可能值得包装您的 malloc/new 调用,以便您在它发生时记录每个分配的文件和行号。 然后在覆盖的删除/释放中将其从列表中删除。

例如(请注意,这是完全未经测试的代码,因此可能无法立即使用)

struct MemoryAllocEntry
{
    char* pFile;
    char* pLine;
};

extern std::map< MemoryAllocEntry > g_AllocList;

inline void* MyMemAlloc( size_t size, char* pFile, char* pLine )
{
    MemoryAllocEntry mae;
    void* pRet = malloc( size );
    mae.pFile = pFile;
    mae.pLine = pLine;

    g_AllocList[pRet] = mae;

    return pRet;
}

inline void MyMemFree( void* pPtr )
{
    std::map< MemoryAllocEntry >::iterator iter = g_AllocList.find( pPtr );
    if ( iter != g_AllocList.end() )
    {
         g_AllocList.erase( iter );
    }
    free( pPtr );
}

#ifdef _DEBUG
    #define malloc( x ) MyMemAlloc( (x), __FILE__, __LINE__ )
    #define free( x ) MyMemFree( (x) )
#endif

然后您需要做的就是单步执行 g_AllocList 以查找任何未完成的分配。 以上显然仅适用于 malloc 和 free 但您也可以使其适用于 new 和 delete (例如,MFC 可以做到)。

回答您的问题,并更新1:

并非所有操作系统都支持不同进程的概念,因此永远不会自动清理。

例如,像 VxWorks 这样的嵌入式操作系统(在某些配置中)不支持多进程的概念,因此即使在您的任务结束后,您未能释放的任何内存仍将保留。 如果这不是故意的,您最终会出现内存泄漏。

此外,这样的平台可能用在很少重新启动且不支持交换的系统上,因此任何内存泄漏都比(例如)台式机上的内存泄漏严重得多。

避免内存泄漏的正确方法是不那么显式地进行内存管理,并依赖于为您管理内容的容器,例如 C++ 中的 STL(它的相关部分)。

使用 C 的低级嵌入式程序员通常通过在启动时静态分配所有内容来避免内存泄漏。 C 程序员还可以使用 alloca() 的堆栈分配来避免可能的内存泄漏(但他们需要准确了解它是如何工作的)。

这是分配给进程的内存。 当你杀死进程时,你会得到它。

内存泄漏通常会导致长时间运行的程序出现问题; 在不幸的地方(例如循环)中仅仅几个字节的泄漏会迅速扩大应用程序的内存占用。

回答编辑 -

如果您的程序要运行,. 所以有些东西然后终止然后不你不需要太担心释放内存。 这对于运行一段时间的程序很重要。 如果您的网络浏览器没有释放用于显示页面的内存,它很快就会使用您计算机中的所有内存。

无论如何,释放内存是一个好习惯,运行一次的小程序有一个习惯,它会变成其他东西,这是一个好习惯。

当程序在 Windows 中完成时,它不仅释放内存,而且释放所有句柄(如果我错了,请纠正我)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM