如何确保在编写C ++代码本身时不会导致任何内存泄漏?

时间:2009-08-20 12:04:56

标签: c++ memory-leaks

运行valgrind或purify将是下一步 但是在编写代码本身的同时,如何确保它不会导致任何内存泄漏? 你可以确保以下事项: - 1:新的等于删除的数量 2:已打开文件描述符是否已关闭

还有别的吗?

13 个答案:

答案 0 :(得分:25)

在任何地方使用RAII惯用法

使用智能指针,例如std :: auto_ptr在适当的地方。 (不要在任何标准集合中使用auto_prt,因为它不会像你想象的那样工作)

答案 1 :(得分:14)

尽量避免动态创建对象。来自Java和其他类似语言的程序员经常写下这样的东西:

string * s = new string( "hello world" );

他们应该写的时候:

string s = "hello world";

类似地,他们在创建值集合时会创建指针集合。例如,如果您有这样的类:

class Person {
   public:
      Person( const string & name ) : mName( name ) {}
      ...
   private:
      string mName;
};

而不是像以下那样编写代码:

vector <Person *> vp;

甚至:

vector <shared_ptr <Person> > vp;

改为使用值:

vector <Person> vp;

您可以轻松添加到这样的矢量:

vp.push_back( Person( "neil butterworth" ) );

并且为您管理Person和vector的所有内存。当然,如果你需要一组多态类型,你应该使用(智能)指针

答案 2 :(得分:7)

答案 3 :(得分:5)

  1. 使用RAII
  2. 隐藏默认副本ctors,operator =()  在每个班级, 除非a)你的班级很琐碎 只使用原生类型,你知道 它总是如此b)你 明确定义你自己的
  3. On 1)RAII,想法是自动删除,如果你发现自己在想“我刚刚打电话给新人,我需要记得在某处调用删除”然后你做错了。删除应该是a)自动或b)放入dtor(以及哪个dtor应该是显而易见的)。

    开2)隐藏默认值。识别流氓默认拷贝ctors等可能是一场噩梦,最简单的方法是通过隐藏它们来避免它们。如果你有一个通用的“root”对象,所有东西都是继承的(无论如何都可以方便调试/分析)隐藏默认值,然后当某个东西试图分配/复制一个继承类时,编译器barfs因为ctor等不是在基类上可用。

答案 4 :(得分:4)

使用STL容器存储数据,最大限度地减少对new的调用。

答案 5 :(得分:3)

我和格伦和贾尔夫一起讨论RAII。

恕我直言,你应该打算写完全删除免费代码。唯一明确的“删除”应该在智能指针类实现中。如果您发现自己想要编写“删除”,请转而找到合适的智能指针类型。如果没有“行业标准”(升级版等)适合您并且您发现自己想要编写一些新的,那么您的架构可能会被打破,或者至少将来会出现维护困难。

我一直认为明确的“删除”是指内存管理“goto”是流控制的。有关详情,请参阅this answer

答案 6 :(得分:1)

当我需要在堆上创建一个新对象时,我总是使用std::auto_ptr

std::auto_ptr<Foo> CreateFoo()
{
   return std::auto_ptr<Foo>(new Foo());
}

即使你打电话

CreateFoo()

它不会泄漏

答案 7 :(得分:1)

基本步骤有两个:

首先,请注意每个新内容都需要删除。因此,当您使用new运算符时,请提高您对该对象将要执行的操作,如何使用以及如何管理其生命周期的了解。

其次,确保永远不会覆盖指针。您可以使用智能指针类而不是原始指针来执行此操作,但如果您确实绝对确定从不将它与隐式转换一起使用。 (一个例子:使用MSXML库,我创建了一个CCOMPtr智能指针来保存节点,获取一个你调用get_Node方法的节点,传入智能指针的地址 - 它有一个返回底层指针类型的转换操作符。不幸的是,这意味着如果智能指针已经保存数据,那个成员数据将被覆盖,泄漏前一个节点。)

我认为这两种情况是你可能泄漏记忆的时候。如果你只是直接使用智能指针 - 永远不允许暴露其内部数据,那么你就可以避免后一个问题。如果你将所有使用new和delete的代码包装在一个类中(即使用RAII),那么你也非常安全。

如果您执行上述操作,则可以非常轻松地避免C ++中的内存泄漏。

答案 8 :(得分:1)

两条简单的经验法则:

  • 永远不要明确地调用delete(在RAII类之外,即)。每个内存分配都应该由RAII类负责,该类在析构函数中调用delete。
  • 几乎从不明确调用new。如果这样做,你应该立即将结果指针包装在智能指针中,该指针取得分配的所有权,并按上述方式工作。

在你自己的RAII课程中,有两个常见的陷阱:

  • 无法正确处理复制:如果复制了对象,谁将获取内存的所有权?他们创建了新的分配吗?你实现了复制构造函数和赋值运算符吗?后者是否处理自我分配?
  • 未考虑异常安全。如果在操作期间抛出异常(例如,赋值)会发生什么?对象是否恢复为一致状态? (它应该总是这样做,无论如何)它是否回滚到它在操作之前的状态? (如果可能的话,它应该这样做)std::vector必须在push_back期间处理这个问题。它可能导致向量调整大小,这意味着1)可能抛出的内存分配,以及2)必须复制所有现有元素,每个元素都可以抛出。像std::sort这样的算法也必须处理它。它必须调用用户提供的比较器,它可能会抛出!如果发生这种情况,序列是否处于有效状态?临时物体是否干净地被破坏了?

如果你在RAII类中处理上述两种情况,那么它们几乎不可能泄漏内存。 如果您使用RAII类来包装所有资源分配(内存分配,文件句柄,数据库连接以及必须获取和释放的任何其他类型的资源),那么您的应用程序不会泄漏内存。

答案 9 :(得分:0)

如果没有人再使用它,请确保释放应用程序创建的共享内存,清理内存映射文件......

基本上,请确保清理应用程序直接或间接创建的任何类型的资源。文件描述符只是应用程序在运行时可能使用的一种资源类型。

答案 10 :(得分:0)

如果你在数据结构的代码中递归地生成任何树或图形,可能会占用你所有的内存。

答案 11 :(得分:0)

有一些静态代码分析工具可以做这种事情; wikipedia是一个开始寻找的好地方。基本上,除了小心并选择正确的容器之外,您无法保证所编写的代码 - 因此需要使用valgrind和gdb等工具。

答案 12 :(得分:0)

在开发周期的早期加入valgrind单元和系统测试,并一致地使用它。