复制堆栈变量时出现异常的析构函数行为

发布于 2024-08-07 11:03:15 字数 770 浏览 6 评论 0原文

我编写了一个测试来检查在对堆栈变量进行覆盖赋值之前是否调用了析构函数,并且我找不到结果的任何合理解释...

这是我的测试(在 Visual C++ 2008 发布模式下):

#include <iostream>
class C {
public:
 char* ptr;
 C(char p) { ptr = new char[100]; ptr[0] = p;}
 ~C() { std::cout << ptr[0] << ' '; delete [] ptr; }
};

int _tmain(int argc, _TCHAR* argv[])
{
 {
  C s('a');
  s = C('b');
  s = C('c');
  s = C('d');
 }
 std::cin.get();
 return 0;
}

我期待如果我的假设为真,则得到“abcd”;如果假设为假,则得到“d”。 相反,我得到“bcdx”。 “x”根据分配给 ptr 的内存量而变化,表明它正在读取随机堆值。

我相信发生的事情(如果我错了,请纠正我)是每个构造函数调用都会创建一个新的堆栈值(让我们称它们为 s1、s2、s3、s4),然后赋值使 s1.ptr 被 s4.ptr 覆盖。 s4 在复制后立即被销毁,但 s1(带有悬空的 ptr)在离开作用域时被销毁,导致 s4.ptr 的双重删除,并且原始 s1.ptr 没有删除。

有没有什么方法可以解决这种不涉及使用shared_ptrs的无益行为?

编辑:将“删除”替换为“删除[]”

I wrote a test to check whether destructors were called before an overwriting assignment on a stack variable, and I can't find any rational explanation for the results...

This is my test (in Visual C++ 2008 Release mode):

#include <iostream>
class C {
public:
 char* ptr;
 C(char p) { ptr = new char[100]; ptr[0] = p;}
 ~C() { std::cout << ptr[0] << ' '; delete [] ptr; }
};

int _tmain(int argc, _TCHAR* argv[])
{
 {
  C s('a');
  s = C('b');
  s = C('c');
  s = C('d');
 }
 std::cin.get();
 return 0;
}

I was expecting to get either "a b c d " if my hypothesis was true, or just "d " if false.
Instead I get "b c d x ". The "x " changes depending on how much memory is allocated to ptr indicating that it's reading random heap values.

I believe what is happening (correct me if I'm wrong) is that each constructor call creates a new stack value(lets call them s1, s2, s3, s4), and then the assignments leave s1.ptr overwritten by s4.ptr. s4 is then destroyed immediately after the copy but s1(with a dangling ptr) is destroyed upon leaving the scope, causing a double delete of s4.ptr and no delete of the original s1.ptr.

Is there any way around this unhelpful behavior that doesn't involve using shared_ptrs?

edit: replaced 'delete' with 'delete []'

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

永言不败 2024-08-14 11:03:15

三法则

您的应用程序行为未定义,因为如上所述多个对象将共享对公共指针的访问,并尝试读取它...

三规则表明,每次定义其中之一时:

  • 复制构造
  • 函数
  • 赋值运算符 析

构函数那么您应该定义另一个,因为您的对象具有特定的行为默认生成的方法不知道。

编辑特殊例外
有时,您定义析构函数只是因为您希望它是虚拟的,或者因为它记录某些内容,而不是因为对您的属性进行了一些特殊处理;)

Rule of Three

Your application behavior is undefined, since as stated multiple objects will share access to a common pointer and will attempt to read it...

The rule of three states that each time you define one of:

  • copy constructor
  • assignment operator
  • destructor

Then you should define the other, since your object has a specific behavior that the default generated methods don't know about.

EDIT special exception:
sometimes you only define the destructor because you want it virtual, or because it logs something, not because there is some special handling of your attributes ;)

嘿咻 2024-08-14 11:03:15

添加其他编译器定义的方法:

class C
{
    public:
      char* ptr;
      C(char p)                { ptr = new char[100]; ptr[0] = p;}
     ~C()                      { std::cout << ptr[0] << ' '; delete [] ptr; }
      C(C const& c)            { ptr = new char[100]; ptr[0] = c.ptr[0];}
      C& operator=(C const& c) { ptr[0] = c.ptr[0]; return *this;}
};

int _tmain(int argc, _TCHAR* argv[])
{
  {
      C s('a');
      s = C('b');
      s = C('c');
      s = C('d');
  }
  std::cin.get();
  return 0;
}

现在应该打印出:

BCD d

每个临时变量都会在表达式末尾被销毁。然后 s 最后被销毁(在将 'd' 复制到 ptr[0] 之后)。如果您在每个方法中添加打印语句,则可以更轻松地查看发生的情况:

>>           C s('a');
Construct 'a'

>>           s = C('b');
Construct 'b'  
Assign 'b' onto 'a'  
Destroy 'b'         (Temporary destroyed at ';')  

>>          s = C('c');
Construct 'c'  
Assign 'c' onto 'b' (was 'a' but has already been assigned over)  
Destroy 'c'         (Temporary destroyed at ';')

>>          s = C('d');  
Construct 'd'  
Assign 'd' onto 'c'  
Destroy 'd'         (Temporary destroyed at ';')  

>> End of scope.
Destroy 'd'         (Object s destroyed at '}')  

由于编译器定义了 4 个方法,因此适用“四规则”。
如果你的类包含一个由该类拥有的RAW指针(拥有意味着你的对象决定了生命周期)。然后您必须重写所有 4 个编译器生成的方法。

由于您创建并销毁了成员“ptr”,因此这是一个拥有的 ptr。因此必须定义所有四种方法。

Add the othere compiler defined methods:

class C
{
    public:
      char* ptr;
      C(char p)                { ptr = new char[100]; ptr[0] = p;}
     ~C()                      { std::cout << ptr[0] << ' '; delete [] ptr; }
      C(C const& c)            { ptr = new char[100]; ptr[0] = c.ptr[0];}
      C& operator=(C const& c) { ptr[0] = c.ptr[0]; return *this;}
};

int _tmain(int argc, _TCHAR* argv[])
{
  {
      C s('a');
      s = C('b');
      s = C('c');
      s = C('d');
  }
  std::cin.get();
  return 0;
}

It should now print out:

b c d d

Each temporary gets destroyed at the end of the expression. Then s gets destroyed last (after having 'd' copied into ptr[0]). If you stick a print statement in each method it beomes easier to see what is happening:

>>           C s('a');
Construct 'a'

>>           s = C('b');
Construct 'b'  
Assign 'b' onto 'a'  
Destroy 'b'         (Temporary destroyed at ';')  

>>          s = C('c');
Construct 'c'  
Assign 'c' onto 'b' (was 'a' but has already been assigned over)  
Destroy 'c'         (Temporary destroyed at ';')

>>          s = C('d');  
Construct 'd'  
Assign 'd' onto 'c'  
Destroy 'd'         (Temporary destroyed at ';')  

>> End of scope.
Destroy 'd'         (Object s destroyed at '}')  

Since there are 4 methods defined by the compiler the "rule of four" applies.
If your class contains a RAW pointer that is owned by the class (owned means your object determines the life span). Then you must override all 4 compiler generated methods.

Since you create and destroy the member 'ptr' this is an owned ptr. Thus all four methods must be defined.

南风起 2024-08-14 11:03:15

由于您在析构函数中进行打印,因此 a 实例将在范围末尾(您看到的 x )被删除。

分配完成后,其他实例将被删除。这解释了 bcdx。

接下来使用

delete [] ptr; 

代替删除

Since you print in the destructor, the a instance will be deleted at the end of the scope (the x you are seeing).

The other instances will be deleted as soon as the assignation is made. that explain the bcdx.

next use the

delete [] ptr; 

instead of delete

℡Ms空城旧梦 2024-08-14 11:03:15

您可以创建一个复制构造函数和赋值运算符,就像对任何拥有原始指针的类型所做的那样。

You could create a copy constructor and assignment operator, as you should do for any type which owns raw pointers.

帅的被狗咬 2024-08-14 11:03:15

s 仅在超出范围时才会被销毁 - 并且正如您所提到的,在程序过程中会被覆盖,因此初始分配被泄漏,最后一个分配被双重删除。

解决方案是重载赋值运算符(并且,正如 Pete 建议的那样,在它们齐头并进时提供一个复制构造函数),在其中您将清理您拥有的数组和给定的副本。

s only gets destroyed when going out of scope - and, as you mention, gets overwritten over the course of the program, so the initial allocation is leaked and the last one is double-deleted.

The solution is to overload the assignment operator (and, as Pete suggests, provide a copy constructor as they go hand in hand), in which you'll clean the array you have and the copy the one you're given.

茶花眉 2024-08-14 11:03:15

问题是您需要复制构造函数和赋值运算符。由于将一个类分配给另一个类的行,因此会生成浅表副本。这将导致两个类具有相同的 ptr 指针。如果其中一个被删除,另一个指向顶部已释放的内存

The problem is that you need copy constructors and assignment operators. Due to the line where you assign one class to the other, a shallow copy is made. This will result in both classes to have the same ptr pointer. If then one of them gets deleted, the other one points top already freed memory

肩上的翅膀 2024-08-14 11:03:15

您尚未定义赋值或复制运算符。所以发生的事情是这样的:

C s('a');

“s”实例被创建并用“a”初始化。

s = C('b');

这会创建一个临时对象,用“b”对其进行初始化,然后启动默认的赋值运算符,对所有变量进行按位复制,覆盖 s 的 ptr。临时对象被销毁。发出“b”并删除“ptr”(使 s 中的 ptr 无效)。

s = C('c');
s = C('d');

又一样。创建临时对象,用“c”初始化,并用临时对象中分配的 ptr 覆盖 s 中的“ptr”。临时对象被销毁,发出“c”并使 s 中的 ptr 无效。重复 d。

  return 0;
}

最后 s 离开作用域,它的析构函数尝试发出 ptr 的第一个字符,但那是垃圾,因为 ptr 是由最后一个('d')临时对象分配和删除的。尝试删除 ptr 应该会失败,因为该内存已被删除。

为了解决这个问题?定义显式复制构造函数和赋值运算符。

class C {
  // other stuff
  C(const C&rhs); // copy constructor
  C& operator=(const c& rhs){ // assignment operator
    a[0] = rhs.a[0];
    return *this;
  }
};

You haven't defined an assignment or copy operator. So whats happening is something like:

C s('a');

The 's' instance gets created and initialized with 'a'.

s = C('b');

This creates a temporary object, initializes it with 'b', and then the default assignment operator kicks in which does a bitwise copy of all variables, overwriting the ptr of s. The temporary object is destroyed. Emitting 'b' and deleting 'ptr' (rendering ptr in s invalid).

s = C('c');
s = C('d');

Same again. Temporary object is created, initialized with 'c', and the 'ptr' in s is overwritten with the ptr allocated in the temporary object. The temporary object is destroyed, emitting 'c' and leaving the ptr in s invalid. Repeat for d.

  return 0;
}

Finally s leaves scope, its destructor tries to emit the first character of ptr, but thats garbage because ptr was allocated and deleted by the last ('d') temporary object. The attempt to delete ptr should fail as that memory is already deleted.

To solve this? Define explicit copy constructors and assignment operators.

class C {
  // other stuff
  C(const C&rhs); // copy constructor
  C& operator=(const c& rhs){ // assignment operator
    a[0] = rhs.a[0];
    return *this;
  }
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文