C ++ debug statement does not work only with VPTR

I am wondering why I get an exception in the delete part in one case here, but not in the other.

Exceptional case

 #include <iostream> using namespace std; class A { public: ~A() { cout << "A dtor" << endl; } }; class B : public A { public: int x; ~B() { cout << "B dtor" << endl; } }; A* f() { return new B; } int _tmain(int argc, _TCHAR* argv[]) { cout << sizeof(B) << " " << sizeof(A) << endl; A* bptr= f(); delete bptr; } 

Herethe 4 1 .. A dtor output is 4 1 .. A dtor , since A has 1 byte for identity, and B has 4 due to int x .

Exception case

 #include <iostream> using namespace std; class A { public: ~A() { cout << "A dtor" << endl; } }; class B : public A { public: virtual ~B() { cout << "B dtor" << endl; } }; A* f() { return new B; } int _tmain(int argc, _TCHAR* argv[]) { cout << sizeof(B) << " " << sizeof(A) << endl; A* bptr= f(); delete bptr; } 

Here the output is 4 1 .. A dtor , since A has 1 byte for identity, and B has 4 due to vptr needed for its virtual destructor. But then in the delete call ( _BLOCK_TYPE_IS_VALID ) , the debug statement is not executed.

Environment

I am running Windows 7 with Visual Studio 2010 SP1Rel.

+4
source share
2 answers

As others have pointed out, you delete an object whose static type is different from its dynamic type, and since the static type does not have a virtual destructor, you get undefined behavior. This includes the behavior of sometimes working and sometimes not working, as you see. However, I think you are interested in a deeper understanding of what is happening with your specific compiler.

Class A has no members, so its data layout is as follows:

 struct A { }; 

Since class B comes from class A , class A becomes embedded in B. When class B has no virtual functions, the layout ends as follows:

 struct B { A __a_part; int x; }; 

The compiler can convert B* to A* by simply taking the address __a_part , as if the compiler had this function:

A * convertToAPointer (B * bp) {return & bp β†’ __ a_part; }

Since __a_part is the first member of B , B* and A* point to the same address.

Code like this:

 A* bptr = new B; delete bptr; 

Effectively doing something like this:

 // Allocate a new B void* vp1 = allocateMemory(sizeof(B)); B* bp = static_cast<B*>(vp1); bp->B(); // assume for a second that this was a legal way to construct // Convert the B* to an A* A* bptr = &bp->__a_part; // Deallocate the A* void* vp2 = ap; deallocateMemory(vp2); 

In this case, vp2 and vp1 same. The system allocates and frees the same memory address, so the program starts without errors.

If class B has a virtual member function (destructor in this case). The compiler adds a pointer to the virtual table, so class B looks like this:

 struct B { B_vtable* __vptr; A __a_part; }; 

The problem here is that __a_part is not the first member, and the convertToAPointer operation convertToAPointer now change the address of the pointer, so vp2 and vp1 no longer point to the same address. Since a different memory location is freed than the allocated one, you get an error.

+1
source

See this post

Short summary:

  • You tell the machine to delete instance A
  • How is this a class that we call through a pointer / link, maybe we should use a virtual table (VT)?
  • There are no virtual members in A, so VT is not used.
  • We call the standard destructor A ...
  • Explosion! We are trying to delete class A, but it happens that the pointer led us to object B, which contains VT, which A did not know about. sizeof (A) is 1 (since AFAIK does not have a legal size of 0) and sizeof (B) is 4 (due to the presence of VT). We want to delete 1 byte, but there is a block of 4 bytes. Due to heap control, DEBUG error was caught.

Of course, the solution is to declare the base class ( A 's) dtor as virtual , so B dtor will always be called.

EDIT: for the first case, here is what the standard has to say:

Β§5.3. In the first alternative (deletion object), if the static type of the object to be deleted is different from its dynamic type, the static type should be the base class of the dynamic type of the object to be deleted, and the static type must have a virtual destructor or undefined behavior . In the second option (delete array), if the dynamic type of the object to be deleted is different from its static type, the behavior is undefined.

Thus, both cases lead us to the sphere of undefined behavior, which, of course, differs from one implementation to another. But it is reasonable that for most implementations, the first case is easier to handle, or at least easier to contemplate, than the second, which is simply an esoteric anti-pattern.

+3
source

All Articles