【问题标题】:How to stop this pointer memory-leak?如何停止此指针内存泄漏?
【发布时间】:2017-10-30 19:22:24
【问题描述】:

我在 c++ 方面表现不错,但在指针和内存方面我一直很糟糕。我遇到了这种情况,不知道有没有解决办法。

typedef unsigned long long ullong;

class MathClass { //This is just an example class
public:
    MathClass() {num = new ullong[1]();}

    MathClass operator+(MathClass b) { //This is not my actual function, just one that has the same problem
        MathClass c;
        c.num[0] = num[0] + b.num[0];
        delete [] num;
        num = NULL;
        return c;
    }
public:
    ullong* num;
};

这适用于这种情况。

MathClass a;
MathClass b;
for (int i = 0; i < 1000; i++) {
    a = a + b;
}

因为我设置 a 等于 a + b,所以当 + 函数运行时,它会将 a 设置为等于 c 并删除旧的 a num。

对于这种情况,它会导致错误,因为我正在删除 b 的 num。

MathClass a;
MathClass b;
MathClass c;
for (int i = 0; i < 1000; i++) {
    a = b + c;
}

如果我不删除 num 这会起作用,但这会导致内存泄漏。当我不删除 num 时,内存很容易超过 100MB。我敢肯定这个问题的答案很简单,但我想不通。

【问题讨论】:

  • 无关:typedef unsigned long long ullong; 总是让我出于某种原因想泡茶。
  • 你有什么理由使用指针和动态分配来处理像整数这样微不足道的事情?这确实增加了难度。您没有显式 Rule of Three violation 的唯一原因是您泄漏了内存。
  • 总是更喜欢使用智能指针而不是原始 new/delete - 这些应该几乎从不在现代 C++ 中使用。
  • 为什么要在除析构函数之外的任何地方删除?
  • 在我的实际项目中,我将指针用作数组。这只是一个具有相同问题的简单类。在这个 MathClass 中,我可以只使用常规的 unsigned long long,但这不是我使用的。

标签: c++ pointers memory-leaks


【解决方案1】:

您需要三(五)条规则。

当你分配内存时,或者如果你需要实现赋值、析构函数或复制构造函数,那么你需要全部三个(五个 - 移动构造函数和移动赋值)。

当您使用 new(考虑 shared_ptr、unique_ptr)分配内存时,您需要控制复制分配和删除的工作方式。

class MathClass { //This is just an example class
public:
    MathClass() {num = new ullong[1]();}
    ~MathClass() { delete [] num;} // cleans up memory.
    MathClass( const MathClass & rhs ) { num = new ullong[1](); num[0] = rhs.num[0]; }
    MathClass& operator=( const MathClass & rhs )
    {
       if( &rhs != this ) {
          num[0] = rhs.num[0];
       }
       return *this;
    }

    MathClass operator+(MathClass b) { //This is not my actual function, just one that has the same problem
    MathClass c;
    c.num[0] = num[0] + b.num[0];
    // the wrong place delete [] num;
    num = NULL;
    return c;
    }
public:
    ullong* num;
};

operator+ 中的 delete [] 位于错误的位置,因为它试图找到正确的位置来释放内存。但是,最简单的方法是应用 rule of 3 并确保内存在构造时构建,在删除时删除,并且赋值(和移动)运算符正常工作

【讨论】:

    【解决方案2】:

    问题实际上并不完全在于指针,而是运算符重载和类实现(正如 cmets 中提到的那样)。 如果您在第一个示例中更改参数顺序(即 a = a + b -> a = b + a ),您将看到与第二个示例相同的错误。所以这里有一篇关于实现operator overloading 的好文章 代码可能是这样的

    #include <iostream>
    #include <algorithm>
    
    typedef unsigned long long ullong;
    
    class MathClass {
    
    public:
        MathClass() { num = new ullong[ 1 ](); }
        MathClass( const MathClass &a ) { 
            *this = a;
        }
        ~MathClass() {  
            delete[] num;
            num = NULL;
        }
    
        MathClass &operator=( const MathClass &a ) {
            if ( this != &a ) {
                num = NULL;
                num = new ullong[ 1 ];
            }
            std::copy( a.num, a.num + 1, num );
    
            return *this;
        }
    
        friend MathClass operator+( const MathClass &a, const MathClass b ) {
            MathClass c;
            c.num[ 0 ] = a.num[ 0 ] + b.num[ 0 ];
    
            return c;
    
        }
    ullong *num;
    };
    
    int main( int argc, char **argv ) {
       MathClass a;
       MathClass b;
       MathClass c;
       for ( int i = 0; i < 1000; ++i ) {
          std::cout << "a.num[ 0 ] is " << a.num[ 0 ] << std::endl;
          std::cout << "b.num[ 0 ] is " << b.num[ 0 ] << std::endl;
          a = b + a;
          a = c + b;
       }
    
       return 0;
    }
    

    在 main "std::cout" 中仅用于输出可见性。复制构造函数的实现肯定比我的要好得多,但这里的关键点是,当您使用复杂类型时,您几乎总是必须重新实现复制和分配运算符,因为它们经常(如果不是总是)被调用。 还有一些其他时刻可以很好地提及您的代码。至少自从 C++11 NULL 更改为 nullptr 之后,typedef 也不那么常见了。使用引用而不是指针,这是一种很好的做法,尤其是在复杂参数的情况下,通过引用而不是通过值传递它们,因为在通过值传递的情况下,将调用复制构造函数,并且您将不得不实例化相同的参数,这可能会导致内存过度使用。

    【讨论】:

    • delete[] num;之后的析构函数中,num只存在几纳秒。 NULLIng 没有意义。与其将复制构造函数基于operator=,不如考虑将operator= 基于复制构造函数。这使您可以在需要时利用Copy and Swap Idiom
    • 同意 num 在析构函数中的存在时间,但将 NULL 分配给它实际上还有另一个原因。如果有人能以某种方式访问​​这个内存地址,那么我们的值就没有了,只有 NULL
    • 感谢有关“与其将复制构造函数基于 operator=,不如考虑将 operator= 基于复制构造函数的建议。这使您可以在需要时利用 Copy and Swap Idiom。”这是一个有趣的想法
    • 我会给你额外的安全作为一个很好的理由。可能值得在deleteing 之前将num 的内容留空。
    • 还建议在删除后将指针设置为NULL,以避免多次删除导致Undefined Behaviour,NULL指针是一种特殊情况。当然在这种情况下,析构函数将被调用一次,指针将被删除一次。
    猜你喜欢
    • 2013-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-13
    • 2013-10-31
    • 2015-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多