我最近正在面试一个C ++职位,有人问我是如何防范创建内存泄漏的。我知道我没有给出那个问题的满意答案,所以我把它扔给你们。防止内存泄漏的最佳方法是什么?
谢谢!
答案 0 :(得分:20)
如果您不需要,请不要在堆上分配内存。大多数工作都可以在堆栈上完成,因此您只应在绝对需要时进行堆内存分配。
如果您需要由另一个对象拥有的堆分配对象,请使用std::auto_ptr
。
使用Boost的标准容器或容器,而不是发明自己的容器。
如果您有一个由其他几个对象引用的对象,并且特别是没有一个对象拥有,那么请使用std::tr1::shared_ptr
或std::tr1::weak_ptr
- 以适合您的用例为准。
如果这些内容都不符合您的使用案例,则可以使用delete
。如果你最终必须手动管理内存,那么只需使用内存泄漏检测工具来确保你没有泄漏任何东西(当然,要小心)。你不应该真的达到这一点。
答案 1 :(得分:20)
到目前为止给出的所有答案归结为:避免拨打delete
。
程序员每次调用delete
时,都会发生潜在的内存泄漏。
而是让delete
调用自动发生。 C ++保证本地对象在超出范围时调用它们的析构函数。使用该保证确保自动删除内存分配。
最常见的是,这种技术意味着每个内存分配都应该包含在一个简单的类中,其构造函数会分配必要的内存,析构函数会释放它。
因为这是一种常用且广泛适用的技术,所以创建了智能指针类来减少样板代码的数量。他们的构造函数不是分配内存,而是指向已经进行的内存分配,并存储它。当智能指针超出范围时,它可以删除分配。
当然,根据用途,可能需要不同的语义。您是否只需要简单的情况,其中分配应该与包装类一样长?然后使用boost::scoped_ptr
或者,如果你不能使用boost,std::auto_ptr
。您是否有未知数量的对象引用分配而不知道每个对象将存在多长时间?然后,参考计数boost::shared_ptr
是一个很好的解决方案。
但您不必使用智能指针。标准库容器也可以解决问题。它们在内部分配存储放入其中的对象副本所需的内存,并在删除时再次释放内存。因此,用户无需拨打new
或delete
。
这种技术有无数种变化,改变了创建初始内存分配的责任,或者应该执行释放。
但他们都有一个共同点就是你的问题的答案: RAII 成语:资源获取是初始化。内存分配是一种资源。当对象被初始化时应该获取资源,并且当对象被销毁时,它应该被对象释放。
让C ++范围和生命周期规则为您完成工作。永远不要在RAII对象之外调用delete
,无论是容器类,智能指针还是单个分配的ad-hoc包装器。让对象处理分配给它的资源。
如果所有delete
次呼叫都自动发生,那么您无法忘记它们。然后你就无法泄漏记忆。
答案 2 :(得分:8)
你最好阅读RAII。
答案 3 :(得分:7)
用shared_ptr替换new。基本上是RAII。使代码异常安全。尽可能使用stl。如果使用引用计数指针,请确保它们不形成循环。来自boost的SCOPED_EXIT也非常有用。
答案 4 :(得分:3)
(简单)永远不要让原始指针拥有对象(在代码中搜索正则表达式"\= *new"
。使用 shared_ptr 或 scoped_ptr 相反,甚至更好,尽可能多地使用实变量而不是指针。
(硬)确保您没有任何循环引用,并且shared_ptrs指向彼此,使用 weak_ptr 来破解它们。
完成!
答案 5 :(得分:2)
使用所有类型的智能指针。
使用某种策略来创建和删除对象,例如谁创建负责删除的对象。
答案 6 :(得分:2)
答案 7 :(得分:2)
除了有关RAII的建议外,如果有任何虚函数,请记得使基类析构函数为虚拟。
答案 8 :(得分:2)
为避免内存泄漏,您必须做的是清楚明确地知道谁负责删除任何动态分配的对象。
C ++允许在堆栈上构造对象(即作为一种局部变量)。这将控制流绑定创建和销毁:在程序执行到达其声明时创建对象,并且当执行转义进行声明的块时销毁对象。每当分配需要匹配该模式时,请使用它。这将为您节省大量的麻烦。
对于其他用法,如果您可以定义并记录一个明确的责任概念,那么这可能正常。例如,您有一个方法或函数返回指向新分配的对象的指针,并且您记录调用者负责最终删除该实例。清晰的文档加上良好的程序员纪律(不容易实现的东西!)可以解决许多内存管理方面的问题。
在某些情况下,包括无纪律的程序员和复杂的数据结构,您可能不得不求助于更高级的技术,例如引用计数。每个对象都被授予“计数器”,这是指向它的其他变量的数量。每当一段代码决定不再指向该对象时,计数器就会减少。当计数器达到零时,将删除该对象。引用计数需要严格的计数器处理这可以通过所谓的“智能指针”来完成:这些是功能指针的对象,但它们会根据自己的创建和破坏自动调整计数器。
在许多情况下,引用计数工作得很好,但它们无法处理循环结构。因此,对于最复杂的情况,你必须求助于重型火炮,即garbage collector。我链接的是由Hans Boehm编写的C和C ++的GC,它已被用于一些相当大的项目(例如Inkscape)。垃圾收集器的目的是维护整个内存空间的全局视图,以了解给定实例是否仍在使用中。当本地视图工具(例如引用计数)不够时,这是正确的工具。有人可能会争辩说,在这一点上,人们应该问问自己C ++是否是解决问题的正确语言。当语言协作时,垃圾收集效果最好(这解锁了许多优化,当编译器不知道内存发生了什么时,这是不可行的,作为典型的C或C ++编译器)。
请注意,上述技术都不允许程序员停止思考。即使GC可能会遭受内存泄漏,因为它使用可达性作为 future usage 的近似值(有理论上的理由暗示它是不可能的,完全一般性,准确地检测之后不会使用的所有物体。您可能仍需要将某些字段设置为NULL
以通知GC您将不再通过给定变量访问对象。
答案 9 :(得分:1)
答案 10 :(得分:1)
一种非常好的方法是使用Smart Pointers,boost / tr1 :: shared_ptr。一旦(堆栈分配)智能指针超出范围,内存将被释放。
答案 11 :(得分:0)
答案 12 :(得分:0)
在x86上,您可以定期使用Valgrind来检查您的代码
答案 13 :(得分:0)
您可以使用该实用程序。 如果你在Linux上工作 - 使用valgrid(它是免费的)。 在Windows上使用deleaker。