【问题标题】:Why is this not a memory leak in C++?为什么这不是 C++ 中的内存泄漏?
【发布时间】:2019-04-21 16:52:40
【问题描述】:

几个月前,我问了this 问题,我问为什么会出现内存泄漏。显然,我忘记了一个虚拟析构函数。

现在我很难理解为什么这不是内存泄漏:

#include <iostream>
#include <vector>
#include <memory>


using namespace std;

class Base{
public:
    explicit Base(double a){
        a_ = a;
    }
    virtual void fun(){
        cout << "Base " << a_ << endl;
    }

protected:
    double a_;
};


class Derived : public Base{
public:
    Derived(double a, double b): Base(a), b_{b}{
    }
    void fun() override{
        cout << "Derived " << a_ << endl;
    }
private:
    double b_;
};



int main() {

    vector<unique_ptr<Base> > m;

    for(int i=0; i<10; ++i){
        if(i%2 == 0){
            m.emplace_back(make_unique<Base>(i));
        }else{
            m.emplace_back(make_unique<Derived>(i, 2*i));
        }
    }

    for(const auto &any:m){
        any->fun();
    }

    return 0;
}

请注意,我没有Base 的虚拟析构函数。

我认为因为我有一个unique_ptr&lt;Base&gt; 类型的向量m,所以只有来自Base 的析构函数被调用,而我在Derived 中的变量b_ 会泄漏,但根据valgrind,情况并非如此。 为什么这不是内存泄漏?

我已经用 valgrind-3.13.0 对此进行了测试

【问题讨论】:

  • 可能有兴趣知道std::shared_ptr 将捕获类型,并调用正确的析构函数。使用std::shared_ptr 而不是std::unique_ptr 会产生额外的开销。但是,确实应该使用正确的智能指针以捕获意图。使用“错误”的方法来捕获类型,所有这些都是为了避免使析构函数虚拟化,这是一条错误的道路(imo)。

标签: c++ memory-leaks valgrind undefined-behavior unique-ptr


【解决方案1】:

由于您的 C++ 实现的行为方式,它不会泄漏内存,但它是未定义的行为,您应该修复它。

在这种情况下,这不是内存泄漏,因为...

  1. std::make_unique 使用new 分配:

    template&lt;class T, class... Args&gt; unique_ptr&lt;T&gt; make_unique(Args&amp;&amp;... args); [...]
    返回: unique_­ptr&lt;T&gt;(new T(std::forward&lt;Args&gt;(args)...))
    [unique.ptr.create]

  2. std::unique_ptr 使用 std::default_delete 解除分配,而 operator delete 使用 operator delete

从内存泄漏的角度来看,类型不同并不重要,因为delete 仍将使用指向由new 分配的对象的指针来调用。

如果Derived 没有一个微不足道的析构函数,这也将是内存泄漏。例如,如果它持有std::vector,则vector 的析构函数将由~Derived 调用。如果不调用,vector 的存储将(可检测到)泄漏。

另见:How does delete work?

但是,根据 C++ 标准,所有这些都是未定义的行为,因此理论上它可能随时停止工作。见[expr.delete/3]

在单对象删除表达式中,如果要删除的对象的静态类型与其动态类型不同,并且选择的释放函数(见下文)不是破坏运算符删除,则静态类型应为基要删除的对象的动态类型和静态类型的类应具有虚拟析构函数或行为未定义。

【讨论】:

    【解决方案2】:

    如果b 是一个由于未定义行为而具有资源(内存、网络...)的对象,则会发生内存泄漏。

    在这里,偶然地,派生的析构函数没有做任何事情,并且对象的内存被正确释放(这次)。但是除了内置/可简单破坏的类型之外的任何东西都可能引发内存泄漏(例如,我建议您尝试使用大小为 10 的 vector)。

    顺便说一句,o 没有使用?

    【讨论】:

    • 我不认为UB可以可靠地导致内存泄漏。除了内置类型之外,还有很多类型可以简单地破坏。
    • @公平。这就是为什么我在第一句话中提到了资源,但后来不够清楚。
    【解决方案3】:

    当基类没有虚拟析构函数时通过多态指针删除对象是未定义的行为。

    未定义的行为可能意味着您的代码泄漏内存、崩溃或正常运行。

    在这种情况下,运行时库可能为您的对象分配了一个内存块,并且即使它被不同类型的指针指向,也能够正确删除该块。对于大多数运行时而言,这可能是正确的,但不能保证。例如。当使用malloc()free() 时,您不需要将malloc() 的大小提供给free(),这里也是如此。

    如果您在 Derived 中定义了析构函数,您会看到它没有被调用。

    【讨论】:

      猜你喜欢
      • 2023-04-03
      • 2015-08-04
      • 2016-08-03
      • 2013-03-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多