Unusual behavior of a destructor when copying across stack variables

I wrote a test to check if the destructors were called before assigning the overwrite to a stack variable and I can't find a 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 expected to get either "abc d" if my hypothesis was true, or just "d" if false. Instead, I get "bcd x". The "X" changes depending on how much memory ptr is allocated, which indicates that it is reading random heap values.

What I believe is happening (correct me if I'm wrong) is that every call to the constructor creates a new stack value (lets you 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 when it leaves the scope, causing s4.ptr to be deleted twice and not deleting the original s1.ptr file.

Is there a way to avoid this useless behavior that doesn't involve using shared_ptrs?

edit: replace 'delete' with 'delete []'

+2


source to share


7 replies


Rule of three

The behavior of your application is undefined as it is stated that multiple objects will share access to the shared pointer and try to read it ...

The rule of three states, which every time you define one of:



  • copy constructor
  • assignment operator
  • destructor

Then you have to define something else, as your object has certain behavior that the generated methods don't know about by default.

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

+11


source


As you are typing in the destructor, the instance will be deleted at the end of the scope (the x you see).

Other instances will be deleted as soon as the redirection is done. which explain bcdx.

then use



delete [] ptr; 

      

instead of deleting

+3


source


Add the methods defined by the othere compiler:

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:

bcdd

Each temporary object is destroyed at the end of the expression. Then s is destroyed last (after "d" is copied to ptr [0]). If you follow the print instructions in each method, it will be easier for you 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 belongs to the class (owned means your object defines lifespan). Then you have to override all 4 compiler generated methods.

Since you are creating and destroying the 'ptr' member, this is owned by ptr. Thus, all four methods must be defined.

+2


source


You can create a copy constructor and an assignment operator just like for any type that the original pointers belong to.

+1


source


The s is only destroyed when it goes out of scope - and, as you say, is overwritten during the program, so the original distribution is leaked out and the last one is deleted twice.

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 clear the array you have and the copy you provided.

+1


source


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

+1


source


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

C s('a');

      

An instance of 's' is created and initialized with 'a'.

s = C('b');

      

Creates a temporary object, initializes it with "b", and then executes a default assignment statement that makes a bitwise copy of all variables, overwriting ptr s. The temporary object is destroyed. Emitting 'b' and removing 'ptr' (rendering ptr to s is invalid).

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

      

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

  return 0;
}

      

Finally, s leaves the region, its destructor tries to emit the first character ptr, but this is garbage because ptr was allocated and removed by the last ('d') temporary object. An attempt to delete ptr should fail because this memory has already been deleted.

To solve this problem? 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;
  }
};

      

0


source







All Articles