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

时间:2009-08-05 09:58:21

标签: c++ c

我们使用malloc在C中分配内存,在C ++中使用new分配内存。我知道分配的内存必须 在C中使用free并在C ++中删除,可以释放或返回给OS。 如果我忘记在分配内存后使用free / delete,则意味着内存泄漏。

现在,我的问题是,这个内存是否仅在程序执行期间泄漏;或者它是永久性泄漏/丢失还是重新启动系统后再次获得?实际上内部过程是什么?内存泄漏/丢失究竟意味着什么?

如果有人能够详细解释这个问题或者给我一些不错的参考资料,我将非常感激。

更新1

在阅读了一些答案之后,我了解到程序终止后内存会返回给OS /系统,如果是这样的话,为什么每个人都需要关心内存泄漏,为什么防止内存泄漏非常重要?

更新2

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

更新3

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

更新4

在阅读答案之后,我现在已经了解了内存泄漏代码的重要性,更少使用新/删除,更多地使用STL,学习了新的东西,如RAII,valgrind和良好的编程实践。 感谢所有人:)

18 个答案:

答案 0 :(得分:32)

per-process 。进程退出后,分配的内存将返回给操作系统,供其他进程(新进程或现有进程)使用。

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

有争议的是,对于寿命短的小进程,内存泄漏是可以容忍的,因为泄漏的内存数量很少而且寿命很短。

请查看this resource,了解可能比您需要的更多信息。我们在这里讨论的是动态分配。

答案 1 :(得分:16)

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

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

因此内存泄漏的问题有两个:

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

许多短期运行的程序实际上忽略了内存泄漏,因为他们知道它很快就会被操作系统清理干净。据我所知,微软的C ++编译器就是这样做的。他们知道,一旦调用了编译器,它最多会运行几分钟。 (并且他们知道它在Windows上运行,一旦进程终止,操作系统回收内存)所以它可以在这里和那里泄漏一些内存。

至于如何避免内存泄漏,请不要创建它们。

每次使用new / delete时都有泄漏内存的风险,因此

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

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

而不是:

char* arr = new char[200];

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

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

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

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

答案 2 :(得分:9)

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

请注意,这可能不适用于某些操作系统,但在任何Windows / unix / mac类型系统上都是如此

答案 3 :(得分:5)

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(和splint),这已被证明是非常宝贵的!

答案 4 :(得分:4)

有检测内存泄漏的工具,例如: Purify

答案 5 :(得分:3)

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

有一种称为资源获取是初始化(RAII)的编程思想。这就是说“如果你需要分配需要删除的内存,或者确保你打开的东西被关闭,将它包装在你在堆栈上创建的对象中。这样当对象超出范围时,析构函数自动被调用,并在析构函数中删除您的资源。“

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

答案 6 :(得分:2)

这是内存泄漏。

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

问题是当指针超出范围并且你没有释放内存然后它被进程分配但是程序无法知道它超出范围而不再需要(不使用像Valgrind这样的工具。

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

答案 7 :(得分:2)

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

更新2:
是的,差不多。但是应该简单地防止内存泄漏,因为它们是一个bug,你永远不应该编写代码,认为bug是可以接受的。

更新3:
防止内存泄漏的最佳方法是首先不要使用malloc / free。如果您正在使用C ++读取RAII,请使用类并复制对象,这将确保您几乎始终没有泄漏。如果必须明确分配内存,请确保跟踪它。如果这意味着你需要一个存储指针的地方的全局对象,那么就这样做。如果它意味着你有一个存储指针的集合类,那么得到一个。永远不要将内存分配给您可能忘记的本地变量,可能从函数返回而不经过免费调用,可能会传递给另一个不被调用的函数。这需要一种纪律感(不需要太多的纪律),但很多人会告诉你,无论如何都要写出好的,正确的,设计良好的,无错误的代码需要同样的优点(而且他们是对的 - 如果你曾经见过与代码设计良好的代码相比被黑客攻击的代码,你将能够立即看到差异。

注意:
即使使用垃圾收集语言,您仍会遇到内存泄漏。人们将对象添加到集合中,然后忘记删除它们,因此对象永远保留在内存中。这算作泄漏。由于人们认为GC将为他们完成所有工作,因此在GC语言中这种做法相当普遍。同样,编码/设计规则是必需的 - 知道你正在做什么将防止这些错误。

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

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

答案 8 :(得分:2)

要补充几点:

  1. 学习正确的工作形式从头开始 - 免费记忆,很难修复坏习惯。
  2. 内存不是应使用new / delete管理或管理的唯一资源。

    例如,某些对象可能包含一些应该在末尾删除的临时文件。通常这些东西绑定到某个对象,因此,如果您忘记删除对象,则忘记取消链接文件和...即使系统重新启动,此资源也不会返回。

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

答案 9 :(得分:1)

内存不会丢失,但会保持分配状态,因此无法用于程序的下一次分配。 这意味着如果程序在不取消分配的情况下继续分配内存,则程序会消耗越来越多的内存。 一段时间后,剩下非未分配的内存,下次尝试分配新内存将失败,因此您的程序会这样做。

此内存取自所谓的“堆”。哪个是您的程序的本地程序,并在程序完成时被完全删除。 因此,程序可以对系统中运行的其他程序造成“唯一”损害,因为操作系统可能也无法分配内存,因为您的程序已“全部耗尽”。一旦你终止你的程序,如果由于分配问题他们没有在同时崩溃,其他人应该正常运行。

答案 10 :(得分:1)

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

答案 11 :(得分:1)

与Purify一起,您可以尝试一种免费的替代方案:valgrind。 规定是valgrind是特定于Linux的解决方案。

答案 12 :(得分:1)

要回答更新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就可以)。

答案 13 :(得分:1)

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

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

例如,像VxWorks这样的嵌入式操作系统(在某些配置中)不支持多个进程的概念,因此即使在任务结束后,任何未能解除分配的内存都将保留。如果不是这样,你最终会发生内存泄漏。

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

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

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

答案 14 :(得分:0)

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

答案 15 :(得分:0)

回答编辑 -

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

无论如何,释放记忆是一种很好的做法,一次运行的小程序习惯于变成其他东西,这是一个很好的习惯。

答案 16 :(得分:0)

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

答案 17 :(得分:0)

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