这是否被认为是内存泄漏?

3
假设您有一个简单的类,如下所示:
class foo{
private:
    int* mData;
    int  mSize;
public:
    foo(int size){
        mSize = size;
        mData = new int [mSize];
    }
    ~foo() {
        mSize = 0;
        delete [] mData;
    }
};

然后在主函数中执行以下操作:

int main () {
    static int HUGE = 100000000;
    foo a(HUGE);
    // do something useful with a
    // .
    // .
    // .
    // Now I'm done with a; I do not need it anymore ...
    foo b(HUGE);
    // do something useful with b
    // Ok we are done with b
    return 0;
}

正如您所见,b之后就不再需要a了,但它是在堆栈上创建的,因此直到程序结束时析构函数才会被调用。我知道这与使用new并忘记调用delete不同,但仍然浪费内存。您认为这算是“内存泄漏”还是糟糕的编程风格?
另外,如何避免这种情况?一种方法是在对象不再需要时手动调用析构函数,但这看起来很丑陋和不熟悉!此外,如果您不修改析构函数并按照原来的方式操作,则可能会出现“double free”的问题。
foo::~foo(){
    if (mData != NULL){
        delete [] mData;
        mData = NULL;
        mSize = 0;
    }
}

另一种方法是通过 foo *pa = new foo(HUGE) 创建堆上的 a,然后在对象不再需要时调用 delete pa。这种方法虽然可行,但有可能引入另一个内存泄漏的风险(如果忘记调用 delete pa)。还有更好的方法来处理不需要的对象吗?

1
我认为这不是一个特别建设性的问题。这完全取决于如何定义“内存泄漏”的含义。你显然知道正在发生什么,无论你是否将其称为“内存泄漏”、“糟糕的编程”或其他任何名称,都不会真正改变什么。哦,而 std::vector 所做的就是你的类试图做的,但它确实做得正确... - Jerry Coffin
2
手动调用析构函数。即使使用您的解决方法,这也会引发未定义的行为。永远不要在同一对象上两次调用析构函数。 - Robᵩ
@Robᵩ 你能否解释一下为什么调用析构函数会引发未定义行为? - mmirzadeh
你不能在对象的生命周期结束后引用它。第一次(直接)调用析构函数会结束对象的生命周期。第二次(隐式)调用操作于一个不存在的对象上。引用2003标准,§3.8/8:“如果程序以自动存储期限结束对象的生命周期……程序必须确保原始类型的对象占据相同的存储位置,当隐式析构函数调用发生时。”你似乎没有在该位置创建新对象,因此对~foo的第二次调用是未定义的。 - Robᵩ
@GradGuy:因为a是自动分配的(“在堆栈上”)。这意味着编译器将在作用域结束时无论你喜欢与否都会调用析构函数。但是,如果您已经手动调用了它,则作用域结束时的调用将导致析构函数被调用两次,这是未定义行为。 - GManNickG
10个回答

10

当一个对象超出其作用域时,析构函数会被调用。在C++中,函数体内允许任意的作用域。请按以下方式编写主函数:

int main () {
    static int HUGE = 100000000;

    {
        foo a(HUGE);
        // do something useful with a
        // Now I'm done with a; I do not need it anymore ...
    }

    {
        foo b(HUGE);
        // do something useful with b
        // Ok we are done with b
    }
    // etc.
    return 0;
}

我看到你的示例已经很简化了,但在真实程序中,别忘了要么

  • foo实现一个合适的复制构造函数和operator=,或者
  • 添加一个私有复制构造函数和operator=的声明,以便它不能被调用。

呸,你打字比我快 :) - Michael Dorgan
关于私有复制构造函数和 operator = 的小技巧不错 :) - mmirzadeh

3

如果您担心作用域问题,只需将巨大的a和b对象放入它们自己的大括号中即可。

虽然这不是技术上的内存泄漏,但正如您所述,这是非常糟糕的内存管理。


{
  {
    foo a(HUGE);
  }
  ...

  {
    foo b(HUGE);
  }

1

不,这绝对不是内存泄漏。

内存泄漏是指当你分配内存并且失去了它的句柄,因此无法在之后释放它。无论何时何地释放内存都没有关系,只要你释放了就行。

你可以添加一个封闭作用域来强制释放内存:

{
    foo a(HUGE);
}
{
    foo b(HUGE);
}

我认为内存泄漏与句柄无关,而是与可用内存的减少有关。 - Dennis C
@DennisCheung,由于标准没有关于内存泄漏的说明或提供明确的定义,因此我们只能参考维基百科 - http://en.wikipedia.org/wiki/Memory_leak。 - Luchian Grigore

1

这不是内存泄漏,因为您没有失去对已分配内存的跟踪。然而,这样做效率稍低,特别是当程序运行时间较长时,应该避免。

您可以使用作用域来缩短对象的生命周期:

int main () {
    static int HUGE = 100000000;
    {
        foo a(HUGE);
        // do something useful with a
        // .
        // .
        // .
        // Now I'm done with a; I do not need it anymore ...
    }
    {
        foo b(HUGE);
        // do something useful with b
        // Ok we are done with b
    }
    return 0;
}

此外,值得重新考虑的是,如果这两部分代码应该在不同的函数中,那么当从函数返回时,分配的对象将被释放。

1
类的构造函数也可以将您在“main()”函数中分配的内存块作为参数。这样,一旦'a'使用完内存块,您就可以将其传递给'b'。 'foo'析构函数根本不需要释放任何内存,您也不需要担心浪费内存或对象生命周期。

0
你认为这算是“内存泄漏”吗?
不算,除非你在中途执行了longjmp之类的操作。
还是说只是糟糕的编程?
我认为在类中使用new[]分配数组是一种糟糕的编程实践,因为你可以使用std::vector。
另外,你如何避免这种情况?
将foo封装到作用域中:
{
    foo a(HUGE);
}

除非你将析构函数更改为类似以下内容的内容:
delete 忽略空指针。析构函数只被调用一次,因此不需要将变量清零。手动调用析构函数是一个非常糟糕的想法——它并不是为此而设计的。如果您想重新初始化结构,请实现clear()或resize()方法。
有没有更好的方法来摆脱不需要的对象?
是的,将它们放入作用域中。

0

提取函数以减少范围。给它们好的名称:

void do_a(int amount)
{
    foo a(amount);
    // ask `a` to be useful
}

void do_b(int amount)
{
    foo b(amount);
    // ask `b` to be useful
}

int main () {
    static int HUGE = 100000000;

    do_a(HUGE);
    do_b(HUGE);

    return 0;
}

0
你认为这是“内存泄漏”还是糟糕的编程?
不,这不是内存泄漏。
你如何避免这种情况?
编写小函数,几行代码。你的代码将更易读,并且在堆栈上分配的未使用变量将被释放。

0

这不是内存泄漏,但它确实是Firefox开发人员花费很长时间修复的内存使用方式。

作用域可能是解决此问题最简单的方法,正如Dark Falcon所建议的那样。或者将分配和相关代码移动到单独的函数中。

此外,指针可以更安全地使用std::auto_ptr进行处理,以便在释放作用域时它们被释放。


0

这不是内存泄漏,但如果你有一个变量在函数的前半部分需要使用,而在后半部分不需要,那么很有可能这个函数正在做太多的事情,应该将其重构为两个(或多个)独立的函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接