【问题标题】:Is memory released when a destructor is called or when `delete` is called?调用析构函数或调用“删除”时是否释放内存?
【发布时间】:2011-11-01 14:09:51
【问题描述】:

假设您有一个class Fool 的对象。

class Fool
{
    int a,b,c;
    double* array ;
    //...
    ~Fool()
    {
        // destroys the array..
        delete[] array ;
    }
};


Fool *fool = new Fool() ;

现在,I know you shouldn't,但无论如何,有些傻瓜会在 fool 上调用析构函数。 fool->~Fool();.

这是否意味着fool 的内存已被释放(即a、b、c 无效)这是否意味着仅在~Fool() 函数中发生任何释放(即数组是只删除?)

所以我想我的问题是,析构函数只是另一个函数,它在对象上调用delete 时被调用,还是它做的更多?

【问题讨论】:

  • 也许值得指出的是,你的类定义不足;只有将new char[] 的结果分配给array 时,析构函数才可能有效。

标签: c++ memory-management destructor


【解决方案1】:

如果你写

fool->~Fool();

您结束了对象的生命周期,这将调用析构函数并回收内部 array 数组。然而,保存对象的内存并没有被释放,这意味着如果你想使用placement new 使对象恢复活力:

new (fool) Fool;

你可以这样做。

根据规范,在显式调用析构函数后读取或写入fool 字段的值会导致未定义的行为,因为对象的生命周期已结束,但仍应分配保存对象的内存,您将需要通过调用operator delete来释放它:

fool->~Fool();
operator delete(fool);

使用operator delete 而不仅仅是写的原因

delete fool;

是后者具有未定义的行为,因为fool 的生命周期已经结束。使用原始释放例程 operator delete 可确保回收内存,而无需尝试执行任何操作来结束对象的生命周期。

当然,如果对象的内存不是来自new(可能是堆栈分配的,或者您使用的是自定义分配器),那么您不应该使用operator delete 来释放它.如果你这样做了,你最终会得到未定义的行为(再次!)。这似乎是这个问题中反复出现的主题。 :-)

希望这会有所帮助!

【讨论】:

  • 我不确定您的描述是否完全对称。如果我说char buf[20000]; Fool * f = new (buf+17) Fool;,那么我绝对可以致电operator delete(f);。这就是重点,分配与构造无关,只有获取内存的她才能决定如何处理它。
  • @Kerrek SB- 啊,是的,这是真的。我指的是 OP 的问题,其中内存明确来自new,但你是绝对正确的。我会去更新我的答案来提及这一点。
  • @Kerrek 当我看到你的示例代码时,我想到了一些关于内存对齐的事情
  • @Kerrek SB- 我不确定我是否理解您的最后评论。我说内部数组被回收是因为在析构函数中,调用了delete[]。我假设该数组已正确分配。我错过了一个重要的细节吗?还是我误解了您的最后评论?
  • @Seth Carnegie- 哎呀......我的意思是new char[sizeof(Fool)]。感谢您接听!
【解决方案2】:

析构函数调用就是这样做的,它调用析构函数。不多也不少。分配与构造是分开的,释放与销毁是分开的。

典型的顺序是这样的:

1. Allocate memory
2. Construct object
3. Destroy object  (assuming no exception during construction)
4. Deallocate memory

事实上,如果你手动运行它,你将自己调用析构函数:

void * addr = ::operator new(sizeof(Fool));
Fool * fp = new (addr) Fool;
fp->~Fool();
::operator delete(addr);

写这个的自动方式当然是Fool * fp = new Fool; delete fp;new 表达式为您调用分配和构造,delete 表达式调用析构函数并释放内存。

【讨论】:

    【解决方案3】:

    这是否意味着傻瓜的内存已被释放(即 a、b、c 无效)或确实 这意味着只发生 ~Fool() 函数中的任何释放(即 数组只被删除?)

    Fool::~Fool()Fool 实例是存储在动态存储中(通过new)还是存储在自动存储中(即堆栈对象)的知识为零。由于在析构函数运行后对象不再存在,因此您不能假设 abcarray 在析构函数退出后仍然有效。

    但是,由于Fool::~Fool()Fool 的分配方式一无所知,因此直接在new 分配的Fool 上调用析构函数不会释放支持该对象的底层内存。

    【讨论】:

      【解决方案4】:

      您应该在调用析构函数后访问abc,即使它是显式的析构函数调用。你永远不知道你的编译器在你的析构函数中放了什么可能使这些值无效。

      但是,在显式析构函数调用的情况下,实际上并没有释放内存。这是设计使然;它允许清理使用放置new 构造的对象。

      例子:

      char buf[sizeof (Fool)];
      Fool* fool = new (buf) Fool;  // fool points to buf
      // ...
      fool->~Fool();
      

      【讨论】:

        【解决方案5】:

        最容易看出析构函数与通过delete 解除分配不同的地方是分配首先是自动的:

        {
          Fool fool;
          // ~Fool called on exit from block; nary a sign of new or delete
        }
        

        还要注意 STL 容器充分利用显式析构函数调用。例如,std::vector<> 将存储和包含对象的生命周期完全分开。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-12-07
          • 2014-12-27
          • 2020-05-25
          • 2012-03-13
          • 1970-01-01
          • 1970-01-01
          • 2012-04-23
          • 1970-01-01
          相关资源
          最近更新 更多