Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the vptr change during destruction?

I was looking at this article, and it says "Upon entry to the base class destructor, the object becomes a base class object, and all parts of C++—virtual functions, dynamic_casts, etc.—treat it that way." Does this mean that the vptr has changed during destruction? How does that happen?

like image 622
pythonic metaphor Avatar asked Oct 27 '11 13:10

pythonic metaphor


People also ask

Why is VPTR not static?

But if we want _vptr to be static, whose responsibility ( compiler/programmer ) to define the _vptr in the program before main. And how programmer knows the pointer of VTABLE to assign it to _vptr. Thats why compiler took that responsibility to assign the value to pointer(_vptr).

What is the significant of VPTR in virtual functions?

VPTR : After creating Vtable address of that table gets stored inside a pointer i.e. VPTR (Virtual Pointer). When you create an object of a class which contains virtual function then a hidden pointer gets created automatically in that object by the compiler.

How many VPTR and Vtable will be created?

There is only one VPTR for each object when using simple inheritance like this. The VPTR must be initialized to point to the starting address of the appropriate VTABLE. (This happens in the constructor, which you'll see later in more detail.)

What will be the functionalities of VPTR and Vtables?

Each entry in this vTable is simply a Function Pointer that points to the most-derived function accessible by that class ie the most Base Class. The compiler also adds a hidden pointer to the base class, which we will call *__vPtr. public: virtual void function2() {};


2 Answers

In all implementations that use virtual function tables (i.e. all current C++ implementations) the answer is yes, the vptr changes to that of the type of the destructor that is being executed. The reason is that the standard requires that the type of the object being destructed is the type of the destructor being exectued.

If you have a hierarchy of three types B, D, MD (base, derived, most derived) and you instantiate and destroy an object of type MD, then while executing MD::~MD() the type of the object is MD, but when the implicit call to the base destructor is executed, the runtime type of the object must be D. This is achieved by updating the vptr.

like image 67
David Rodríguez - dribeas Avatar answered Sep 21 '22 06:09

David Rodríguez - dribeas


The pedantic C++ answer is, of course, "The Standard doesn't say anything about vtbls or how polymorphism is implemented."

However, practically speaking, yes. The vtbl is modified before the body of the base class' destructor begins execution.

EDIT:

Here is how I used MSVC10 to see this happen for myself. First, the test code:

#include <string>
#include <iostream>
using namespace std;

class Poly
{
public:
    virtual ~Poly(); 
    virtual void Foo() const = 0;
    virtual void Test() const = 0 { cout << "PolyTest\n"; }
};

class Left : public Poly
{
public:
    ~Left() 
    { 
        cout << "~Left\n"; 
    }
    virtual void Foo() const {  cout << "Left\n"; }
    virtual void Test() const  { cout << "LeftTest\n"; }
};

class Right : public Poly
{
public:
    ~Right() { cout << "~Right\n"; }
    virtual void Foo() const { cout << "Right\n"; }
    virtual void Test() const { cout << "RightTest\n"; }
};

void DoTest(const Poly& poly)
{
    poly.Test();
}

Poly::~Poly() 
{  // <=== BKPT HERE
    DoTest(*this);
    cout << "~Poly\n"; 
}

void DoIt()
{
    Poly* poly = new Left;
    cout << "Constructed...\n";
    poly->Test();
    delete poly;
    cout << "Destroyed...\n";
}

int main()
{
    DoIt();
}

Now, set a breakpoint at the opening brace for the Poly dtor.

When you run this code at it breaks on the opening brace (just before the body of the constructor begins executing), you can take a peek at the vptr:enter image description here

Also, you can view the disassembly for the Poly dtor:

Poly::~Poly() 
{ 
000000013FE33CF0  mov         qword ptr [rsp+8],rcx  
000000013FE33CF5  push        rdi  
000000013FE33CF6  sub         rsp,20h  
000000013FE33CFA  mov         rdi,rsp  
000000013FE33CFD  mov         ecx,8  
000000013FE33D02  mov         eax,0CCCCCCCCh  
000000013FE33D07  rep stos    dword ptr [rdi]  
000000013FE33D09  mov         rcx,qword ptr [rsp+30h]  
000000013FE33D0E  mov         rax,qword ptr [this]  
000000013FE33D13  lea         rcx,[Poly::`vftable' (13FE378B0h)]  
000000013FE33D1A  mov         qword ptr [rax],rcx  
    DoTest(*this);
000000013FE33D1D  mov         rcx,qword ptr [this]  
000000013FE33D22  call        DoTest (13FE31073h)  
    cout << "~Poly\n"; 
000000013FE33D27  lea         rdx,[std::_Iosb<int>::end+4 (13FE37888h)]  
000000013FE33D2E  mov         rcx,qword ptr [__imp_std::cout (13FE3C590h)]  
000000013FE33D35  call        std::operator<<<std::char_traits<char> > (13FE3104Bh)  
}
000000013FE33D3A  add         rsp,20h  
000000013FE33D3E  pop         rdi  
000000013FE33D3F  ret  

Step over the next line, in to the body of the destructor, and take another peek at the vptr:

enter image description here Now when we call DoTest from within the body of the destructor, the vtbl has already been modified to point to purecall_, which generates a runtime assertion error in the debugger:

like image 22
John Dibling Avatar answered Sep 19 '22 06:09

John Dibling