【问题标题】:Providing a pointer of an object to a constructor causes object to be destructed [duplicate]将对象的指针提供给构造函数会导致对象被破坏[重复]
【发布时间】:2017-12-12 09:46:03
【问题描述】:

我在我的一个项目中发现了一种奇怪的行为。具体情况如下:

  • 我有一个对象。让我们调用Victim,它包含一个指针变量、一个构造函数和一个析构函数。
  • 我有另一个对象,我们调用Perpetrator,它的构造函数接受Victim对象,并将指针变量复制到里面的具体变量。
  • 我创建一个Victim*,并使用new 创建对象,然后通过Perpetrator(*victim) 将其提供给Perpetrator
  • Perpetrator 的构造函数完成后,Victim 的析构函数被调用并删除对象。

问题是Victim的唯一副本,也就是poor在构建过程中被完全销毁。最后通过delete poor整理程序会导致double-free错误。

该行为在 C++98/11、GCC 4.8.5、7.x 和 LLVM CLANG 中是一致的,因此必须明确定义。这种行为叫什么,它的语义是什么?

我的理论是,由于 constructor 接受一个具体的对象,它被认为是复制的,所以当 constructor / function 完成时它被破坏了。

既然我表扬PoC||GTFO,代码如下:

澄清:示例是有意编写的,因为它是一个更复杂但无泄漏且管理良好的数据结构复杂体的简化模型。删除所有必要的位使它看起来像一个可怕的破代码。在实际代码中,Victim 是长期存在的数据存储,Perpetrator 是用于处理所述数据的临时存储变量。

#include <iostream>

using namespace std;

struct Victim
{
  double* pointer;

  Victim ()
  {
    this->pointer = new double(42.2);
  }

  ~Victim ()
  {
    cout << "Destructor of the Victim is called." << endl;
    delete this->pointer;
  }

};

struct Perpetrator
{
  double concrete;

  Perpetrator (Victim victim)
  {
    concrete = *(victim.pointer);
  }
};


int main ()
{
  Victim* poor = new Victim();
  Perpetrator cruel(*poor);

  cout << cruel.concrete << endl;
}

样本输出:

./destructor_test 
Destructor of the Victim is called.
42.2

【问题讨论】:

  • 0/3/5 规则被打破。 double* pointer; 应该只是 double value;,所以没有内存管理。
  • 示例是故意这样写的。我想证明良性副本可以破坏可能计划使用更长时间的对象。

标签: c++ c++11 constructor


【解决方案1】:

Perpetrator (Victim victim) - 按值传递一个对象。意味着它必须被(复制)构造,然后在调用完成时销毁

事实上,根本不需要使用new。这个:

int main ()
{
  Victim poor;
  Perpetrator cruel(poor);

  cout << cruel.concrete << endl;
}

作用类似。你会看到两个构造和两个破坏。您的原始代码没有显示第二个,因为您的示例泄漏。

【讨论】:

  • 我故意将示例建模为泄漏。在我的示例中,您无法重新删除受害者,因为它已经完全删除。所以它在实践中不会泄漏,但会留下破坏的痕迹。因此,Victim 在示例中没有复制,而是在普通的“按值复制”场景中复制。
  • @bayindirh - “因为它已经完全删除了”不,不是。
  • 是的。如果您在末尾添加delete poor,您将收到双重免费错误。 Victim 的唯一副本,即poor 在构造过程中被破坏。
  • @bayindirh - 双重删除错误是因为您双重删除 this-&gt;pointerVictim 对象本身非常容易泄漏。
  • @bayindirh 您可能想尝试将复制构造函数添加到victim,然后在最后执行delete poor 并查看结果。
【解决方案2】:

通过参考传递受害者:

struct Perpetrator
{
    double concrete;

    Perpetrator (Victim& victim)
    {
        concrete = *(victim.pointer);
    }
};

每个受害者将建造和破坏一次。

【讨论】:

    【解决方案3】:

    这是您想要达到的目标吗?

    using namespace std;
    
    struct Victim
    {
      double* pointer;
    
      Victim ()
      {
        this->pointer = new double(42.2);
      }
    
      ~Victim ()
      {
        cout << "Destructor of the Victim is called." << endl;
        delete this->pointer;
      }
    };
    
    struct Perpetrator
    {
      double concrete;
    
      Perpetrator (Victim *victim)
      {
        concrete = *(victim->pointer);
      }
    
        ~Perpetrator ()
      {
        cout << "Destructor of the Perpetrator is called." << endl;
      }
    };
    
    
    int main ()
    {
      Victim* poor = new Victim();
      Perpetrator cruel(poor);
      cout << cruel.concrete << endl;
      delete poor;
    }
    

    输出: 42.2
    调用了 Victim 的析构函数。
    施暴者的析构函数被调用。

    【讨论】:

      猜你喜欢
      • 2011-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-29
      • 1970-01-01
      • 2014-08-12
      • 2015-05-12
      相关资源
      最近更新 更多