vptr在销毁过程中会改变吗?

发布于 2024-12-12 08:24:46 字数 178 浏览 0 评论 0原文

我正在看这篇文章,它说“进入基类析构函数后,该对象成为基类类对象,以及 C++ 的所有部分——虚拟函数、dynamic_casts 等——都是这样对待的。”这是否意味着 vptr 在销毁过程中发生了变化?这是怎么发生的?

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?

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

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

发布评论

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

评论(2

香草可樂 2024-12-19 08:24:46

在所有使用虚函数表的实现中(即所有当前的 C++ 实现),答案是肯定的,vptr 更改为正在执行的析构函数的类型。原因是标准要求被析构的对象的类型是被执行的析构函数的类型。

如果您具有 B、D、MD 三种类型(基础、派生、最派生)的层次结构,并且实例化并销毁 MD 类型的对象,则在执行 MD::~MD 时() 对象的类型 MD,但是当执行对基析构函数的隐式调用时,对象的运行时类型必须是 D。这是通过更新vptr来实现的。

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.

爱*していゐ 2024-12-19 08:24:46

当然,迂腐的 C++ 答案是“该标准没有提及任何有关 vtbl 或多态性如何实现的内容”。

然而,实际上来说,是的。 vtbl 在基类析构函数体开始执行之前被修改。

编辑:

以下是我如何使用 MSVC10 亲眼目睹这种情况的发生。首先是测试代码:

#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();
}

现在,在 Poly dtor 的左大括号处设置一个断点。

当您运行此代码时,它在左大括号处中断(就在构造函数的主体开始执行之前),您可以查看 vptr:在此处输入图像描述

此外,您还可以查看 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  

跨过下一行,进入析构函数的主体,然后再看一下虚拟指针:

在此处输入图像描述
现在,当我们从析构函数体内调用 DoTest 时,vtbl 已被修改为指向 purecall_,这会在调试器中生成运行时断言错误:

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:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文