【问题标题】:Why absence of a virtual destructor doesn't cause a memory leak? [duplicate]为什么没有虚拟析构函数不会导致内存泄漏? [复制]
【发布时间】:2019-11-09 13:59:33
【问题描述】:

我在玩代码

struct A {
    char a[20000];
    A() { a[0] = 'A'; }
    ~A() {}
};
struct B : A {
    char a[20000];
    B() { a[0] = 'B'; }
    ~B() {}
};
int main() {
    A *pA = new A;
    A *pB = new B;
    delete pA;
    delete pB;
    return 0;
}

有些人写道 (why do we need a virtual destructor with dynamic memory?) 应该会导致内存泄漏,但事实并非如此。 我使用 g++,然后使用 valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind- out.txt 并获取

HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 3 allocs, 3 frees, 132,704 bytes allocated
All heap blocks were freed -- no leaks are possible

我知道一些旧的编译器在类似情况下尝试释放内存时会出现问题,但看起来现代 C++ 在这种情况下可以无缝地释放内存。所以我很好奇怎么可能?也许 delete 使用操作系统为给定分配的内存块提供的信息?

编辑。如果我们有空的析构函数和构造函数,我仍然不清楚究竟是什么导致了 delete *pB 的 UB。问题的答案 (Missing Virtual Destructor Memory Effects) 表明没有任何 UB。

【问题讨论】:

  • 未定义的行为是未定义的。它可能会做你想做的事,也可能做其他事情。
  • 非常感谢您的链接。
  • "也许 delete 使用操作系统为给定分配的内存块提供的信息?"不,这是一个实现细节,可能因平台而异。据我所知,没有任何平台会使用操作系统提供的信息来做类似的事情。在我的平台上,分配器有一些簿记,它跟踪为该指针在堆中为对象分配了多少字节(在调试版本中为栅栏分配了额外的内存,new、@987654326 有单独的堆@ 和 malloc)。

标签: c++ memory-management memory-leaks virtual destructor


【解决方案1】:

我假设您已经知道 delete pB 是未定义的行为。但是,为什么它不会泄漏您机器上的内存?这与您实现的动态内存管理的工作方式有关。

这是一种可能性:在某些实现中,默认全局 operator newoperator delete 通过调用 C 库 mallocfree 函数来工作。但是free 需要能够在它刚刚通过没有类型或大小信息的void* 时完成它的工作,这意味着malloc 必须在它返回之前在某处“写下”大小。在您的程序中,对new B 的调用可能会导致malloc 记下B 的大小,以便稍后将free 传递给指针时,它会确切知道要释放多少字节。

【讨论】:

    【解决方案2】:

    为什么没有虚拟析构函数不会导致内存泄漏?

    因为通过指向析构函数不是虚拟的基类的指针来销毁对象的行为是未定义的。当行为未定义时,没有任何保证。例如,不能保证内存会泄漏。

    也就是说,尚不清楚为什么会有内存泄漏的预期。如果你看一下B 的析构函数,你会注意到它什么都不做——主体是空的,成员有一个普通的析构函数。没有理由期望不运行不执行任何操作的函数会导致内存泄漏。

    【讨论】:

      【解决方案3】:

      您在 A 或 B 上没有任何内存分配,那么为什么会有泄漏呢? 当调用不正确的析构函数(由于不是虚拟的)时,就会出现泄漏。

      如果析构函数不需要做任何事情,因为无论如何都没有什么要清除的,那么就不会存在泄漏。

      a[20000] 更改为 *a 并新建/删除它,看看会发生什么。

      顺便说一句,有一个未定义的行为,但这个问题更多地与 OP 不了解析构函数的工作原理有关。这不是链接问题的完全重复。

      struct A {
          char* a;
          A() { a = new char[20000]; a[0] = 'A'; }
          ~A() { delete[] a;}
      };
      struct B : A {
          char* b;
          B() : A() { b = new char[20000]; b[0] = 'B'; }
          ~B() { delete[] b;}
      };
      int main() {
          A *pA = new A;
          A *pB = new B;
          delete pA;
          delete pB;
          return 0;
      }
      

      【讨论】:

      • 但是上述页面中的示例也错过了任何具有内存释放的析构函数。
      • 析构函数不清除被删除对象的内存;这就是 delete/new 所做的。无论调用哪个析构函数,都会释放原来的内存;有问题的是需要销毁的成员。
      • @vollitwr “提到页面上的示例”说 “请假设一些构造函数和析构函数。” 这意味着 int *xint *y 拥有指向动态分配的指针构造函数和析构函数分别分配和释放的内存。您最初的示例没有这样做 - char a[2000] 不是动态分配的,而是类足迹的一部分。
      • @vollitwr 对于它的价值,我同意在您链接到的答案中省略构造函数/析构函数是令人困惑的。
      • @vollitwr 为了您的方便:eel.is/c++draft/expr.unary#expr.delete-3
      猜你喜欢
      • 2016-04-02
      • 2012-02-03
      • 2012-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-08
      • 2013-10-03
      • 1970-01-01
      相关资源
      最近更新 更多