Почему в виртуальной таблице есть два виртуальных деструктора и где находится адрес не виртуальной функции (gcc4.6.3)

Я выполнил простой тест, чтобы проверить ранжирование памяти класса Derived, поэтому я обнаружил, что в виртуальной таблице класса Derive есть два виртуальных адреса деструктора. Может кто-нибудь объяснить это мне?

код:

#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

являются деструкторами производного класса. Почему есть два?

И у меня есть другой вопрос: где адрес не виртуальной функции-члена? Я обнаружил, что адрес не виртуальной функции не существует в памяти производного класса. Где это?

Ответы

Ответ 1

Адрес не виртуальной функции-члена, ну, вы сказали, он не виртуальный, что означает, что он не должен находиться в виртуальной таблице. Зачем? Ну, это не зависит от типа времени выполнения объекта, а только от статического типа, означающего, что компилятор может определить во время компиляции, какую функцию вызывать, чтобы вызов был разрешен, вместо того, чтобы использовать последнее связывание во время выполнения. Сама функция находится в разделе кода где-то, и поэтому во время компиляции адрес функции вставляется непосредственно на сайт вызова.

Хорошо, теперь на забавные вещи. Я немного искал в списке наблюдателей на визуальной студии, и вот что я нашел:

|---------------------------|
|          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), мы получим:

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

Хорошо, поэтому я запустил этот код в gcc, используя QtCreator для Windows с -fdump-class-hierarchy, и это дало мне:

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

Итак, мы можем ясно видеть, что действительно есть две записи, которые являются деструкторами класса Derive. Это все еще не отвечает, почему все же мы ищем то, что искали. Ну, я нашел это в GCC Itanium ABI

Записи для виртуальных деструкторов являются фактически парами записей. Первый деструктор, называемый полным деструктором объекта, выполняет уничтожение без вызова delete() объекта. Второй деструктор, называемый удаляющим деструктором, вызывает delete() после уничтожения объекта. Оба уничтожают любые виртуальные базы; отдельная не виртуальная функция, называемая деструктором базового объекта, выполняет уничтожение объекта, но не его виртуальные базовые подобъекты, и не вызывает delete().

Итак, обоснование того, почему есть два, похоже, таково: Скажем, у меня есть A, B. B наследуется от A. Когда я вызываю delete на B, удаление виртуального деструктора является вызовом для B, но не удаление один будет вызываться для A, иначе будет двойное удаление.

Я лично ожидал, что gcc будет генерировать только один деструктор (не удаляющий один) и вместо этого вызывать delete. Вероятно, это то, что делает VS, поэтому я нашел только один деструктор в моей таблице vtable, а не два.

Хорошо, я могу спать сейчас:) Надеюсь, это удовлетворит ваше любопытство!

Ответ 2

  1. Почему их два?

Это должен быть конкретный случай для вашего компилятора, я сделал то же самое в g++ и привел только один вызов деструктора.

~Derive
~Base2
~Base1

ссылка на результаты: https://ideone.com/vzZggh

  1. Где находится адрес не виртуальной функции-члена?

да, для не виртуальных функций-членов вам нужно перейти в базовый класс.

Адрес не виртуальной функции не существует в памяти производного класса.