why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)

后端 未结 2 1155
小蘑菇
小蘑菇 2021-02-01 16:51

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 expla

2条回答
  •  伪装坚强ぢ
    2021-02-01 17:08

    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.

    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)        |
    |---------------------------|
    

    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)        |
    |---------------------------|
    

    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). enter image description here EDIT: EUREKA!

    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
    

    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

    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().

    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.

    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!

提交回复
热议问题