为什么在虚拟表中有两个虚拟析构函数,以及非虚拟函数的地址在哪里(gcc4.6.3) [英] why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)

查看:76
本文介绍了为什么在虚拟表中有两个虚拟析构函数,以及非虚拟函数的地址在哪里(gcc4.6.3)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我实现了一个简单的测试来检查Derived类的内存等级,因此我发现Derive类的虚拟表中有两个虚拟析构函数地址.有人可以向我解释吗?

I implemented a simple test to check the memory rank of Derived class, so I find there are two virtual destructor address in the virtual table of Derive class. Can someone explain it to me?

#include<iostream>
#include<ostream>
#include<cstdio>
using namespace std;

class Base1
{
    public:
        Base1():a(1){}
        virtual ~Base1()
        {
            cout << "~Base1"  << endl;
        }
        int a;
        virtual void print()
        {
            cout << "I am base 1!" << endl;
        }
};

class Base2
{
    public:
        Base2():b(2){}
        virtual ~Base2(){
            cout << "~Base2" << endl;
        }
        int b;
        virtual void print()
        {
            cout << "I am base 2!" << endl;
        }
};

class Derive : public Base1, public Base2
{
    public:
        Derive():c(3){}
        virtual ~Derive(){
            cout << "~Derive" << endl;
        }
        int c;
        virtual void print()
        {
            cout << "I am Derive!!" << endl;
        }
        void prints()
        {
            cout << "I am not virtual!!" << endl;
        }
};

int main()
{
    typedef void (*Func) (void);
    Derive *d = new Derive();
    int **p = (int **)(d);
    Func f = (Func)(p[0][0]);
    //int s = (int)(*(p + 3));
    Func f2 = (Func)(p[0][1]);
    //cout << p << endl;
    //cout << s << endl;
    f();
    //cout.flush();
    f2();
    //f();
    return 0;
}

我找到了

f() and f2()

结果如下:

~Derive
~Base2
~Base1
~Derive
~Base2
~Base1

是派生类的析构函数.为什么有两个?

are destructors of derived class. Why are there two?

还有一个问题:非虚拟成员函数的地址在哪里?我发现派生类的内存中不存在非虚拟函数地址.在哪里?

And I have another question: Where is the address of the non-virtual member function? I find the non-virtual function address does not exist in the memory of the derived class. Where is it?

推荐答案

非虚拟成员函数的地址,正如您所说的那样,它不是虚拟的,这意味着它不必位于虚拟表中.为什么?好吧,它不依赖于对象的运行时类型,而仅依赖于静态类型,这意味着编译器可以在编译时找出要调用的函数,以便解决该调用,然后在执行过程中不使用后期绑定.该函数本身位于代码部分中的某个位置,因此在编译时,函数地址直接插入到调用站点中.

The address of the non-virtual member function, well you said it, it's not virtual which means it doesn't need to be in the virtual table. Why? Well it doesn't depend on the runtime type of the object, only the static type meaning the compiler can figure out at compile time which function to call so the call is resolved then instead of using late binding during execution. The function itself is in the code section somewhere and so at compile time the functions address is inserted at the call site directly.

现在确定有趣的东西.我在Visual Studio监视列表中进行了一些挖掘,这是我发现的内容:

Ok now onto the fun stuff. I did some digging around in the visual studio watch list and here is what I found:

|---------------------------|
|          Derive           |
|---------------------------|
| vtable ptr for Base1 (+0) |
| Base1::a (+4)             |
|---------------------------|
| vtable ptr for Base2 (+8) |
| Base2::b (+12)            |
|---------------------------|
| Derive::c (+16)           |
|---------------------------|

|---------------------------|
|       Base1 vtable        |
|---------------------------|
| Derive::destructor (+0)   |
| Derive::print (+4)        |
|---------------------------|

|---------------------------|
|       Base2 vtable        |
|---------------------------|
| Derive::destructor (+0)   |
| Derive::print (+4)        |
|---------------------------|

是的,您有两次析构函数,基本上每个碱基一次.如果删除Derive的第二个基数(使其仅继承自Base1),我们将得到:

So yeah you have your destructor twice, once per base essentially. If I remove the second base of Derive (making it only inherit from Base1) we get:

|---------------------------|
|          Derive           |
|---------------------------|
| vtable ptr for Base1 (+0) |
| Base1::a (+4)             |
|---------------------------|
| Derive::c (+8)            |
|---------------------------|

|---------------------------|
|       Base1 vtable        |
|---------------------------|
| Derive::destructor (+0)   |
| Derive::print (+4)        |
|---------------------------|

这是监视列表和本地人"窗口的屏幕快照.如果您查看监视列表中的值,您会发现对象Derive的开头与a的地址之间存在一个间隙,这就是第一个vtable所适合的位置(Base1的那个).其次,您会在a和b之间找到相同的间隙,这就是第二个vtable所适合的位置(Base2的那个). EUREKA!

Here is a screenshot of the watch list and locals window. If you take a look at the values in the watch list you'll see theres a gap between the start of the object Derive and the address of a, that is where the first vtable fits (the one for Base1). And secondly you'll find the same gap between a and b, that's where the second vtable fits (the one for Base2). EUREKA!

好的,所以我在Windows上使用QtCreator在-fdump-class-hierarchy上的gcc中运行了这段代码,这给了我:

Alright, so I ran this code in gcc using QtCreator on Windows with -fdump-class-hierarchy and this gave me:

Vtable for Derive
Derive::_ZTV6Derive: 10u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI6Derive)
8     (int (*)(...))Derive::~Derive
12    (int (*)(...))Derive::~Derive
16    (int (*)(...))Derive::print
20    (int (*)(...))-8
24    (int (*)(...))(& _ZTI6Derive)
28    (int (*)(...))Derive::_ZThn8_N6DeriveD1Ev
32    (int (*)(...))Derive::_ZThn8_N6DeriveD0Ev
36    (int (*)(...))Derive::_ZThn8_N6Derive5printEv

因此,我们可以清楚地看到确实有2个条目是Derive类的析构函数.这仍然不能回答为什么一直是我们一直在寻找的原因.好吧,我在 GCC的Itanium ABI

So we can clearly see that there are indeed 2 entries which are destructors of class Derive. This still doesn't answer why yet which is what we've been searching for all along. Well, I found this in GCC's Itanium ABI

虚拟析构函数的条目实际上是成对的条目.第一个析构函数,称为完整对象析构函数,执行破坏操作而无需在对象上调用delete().第二个析构函数(称为删除析构函数)在销毁对象后调用delete().两者都摧毁了任何虚拟基地.一个单独的非虚拟函数(称为基础对象析构函数)执行对象的销毁操作,但不执行其虚拟基础子对象的销毁操作,并且不调用delete().

The entries for virtual destructors are actually pairs of entries. The first destructor, called the complete object destructor, performs the destruction without calling delete() on the object. The second destructor, called the deleting destructor, calls delete() after destroying the object. Both destroy any virtual bases; a separate, non-virtual function, called the base object destructor, performs destruction of the object but not its virtual base subobjects, and does not call delete().

所以,为什么有两个原因似乎是这样的:说我有A,B.B从A继承.当我在B上调用delete时,删除的虚拟析构函数是对B的调用,但是不删除一个将被称为A,否则将被双重删除.

So, the rationale for why there are two seems to be this: Say I have A, B. B inherits from A. When I call delete on B, the deleting virtual destructor is call for B, but the non-deleting one will be called for A or else there would be a double delete.

我个人希望gcc仅生成一个析构函数(一个非删除的析构函数),然后再调用delete. VS可能就是这样做的,这就是为什么我在vtable中只找到一个析构函数,而不是两个的原因.

I personally would have expected gcc to generate only one destructor (a non-deleting one) and call delete after instead. This is probably what VS does, which is why I was only finding one destructor in my vtable and not two.

好的,我现在可以上床睡觉了:)希望这能满足您的好奇心!

Alright I can go to bed now :) Hope this satisfies your curiosity!

这篇关于为什么在虚拟表中有两个虚拟析构函数,以及非虚拟函数的地址在哪里(gcc4.6.3)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆