KirillHelm
@KirillHelm

Как работают указатели на таблицу виртуальных функций?

TL;DR
Решил изучить устройство таблиц виртуальных функций, т.к. слышал что об этом могут спрашивать на собеседовании.
Естественно начал экспериментировать с размером класса для интереса. Сам собой, нашелся хитроватый примерчик, который и дал начало моему вопросу.

Сразу отмечу, что компилирую только с помощью GCC и стараюсь придерживаться стандарта. Я в целом понимаю, как происходит управление памятью при создании/удалении объектов класса. Но вот не нашел в стандарте ни слова о том как создается таблица виртуальных функций в таком случае.

Описание задачи
У нас есть пустой класс. По стандарту c++, размер его объектов будет равен минимально адресуемой единице - 1 байту.
После добавления виртуальной функции размер объекта уже будет равен 4 байтам - размеру указателя на таблицу виртуальных функций.
Пример №1
class A
{
    //sizeof(BaseA) = 1 byte
}

class B
{
    virtual void foo() {...}
    //sizeof(B) = 4 byte
}



Теперь усложняем пример и добавим еще два класса, в каждом по виртуальной функции, а потом отнаследуемся от них пустым классом. Получим вот что:
Пример №2
class BaseA
{
    virtual void foo()
    {

    }
};

class BaseB
{
    virtual void foo()
    {

    }
};

class Derived : BaseA, BaseB
{

};



Сколько будет занимать объект класса Derived? Пока очевидно, что это будет 4byte указатель на ТВФ класса BaseA + 4byte указатель на ТВФ BaseB, и того:
sizeof(Derived) = 8

Тут-то и начинается самое интересное, сколько памяти будет занимать объект класса Derived со своей собственной виртуальной функцией?
Пример №3
#include <iostream>
using namespace std;

class BaseA
{
    virtual void foo()
    {

    }
};

class BaseB
{
    virtual void foo()
    {

    }
};

class DerivedWithVirtual : BaseA, BaseB
{
    virtual void bar()
    {

    }
};

class DerivedWithoutVirtual : BaseA, BaseB
{

};

int main()
{
    cout << "Size of Base class A: "
         << sizeof(BaseA) << endl;

    cout << "Size of Base class B: "
         << sizeof(BaseB) << endl;

    cout << "Size of derived class with virtual function: "
         << sizeof(DerivedWithVirtual) << endl;

    cout << "Size of derived class without virtual function: "
         << sizeof(DerivedWithoutVirtual) << endl;

    system("pause");

    return 0;
}



Вот что он выведет:
Вывод примера №3
Size of Base class A: 4
Size of Base class B: 4
Size of derived class with virtual function: 8
Size of derived class without virtual function: 8

Сам вопрос
Почему у объектов класса DerivedWithVirtual не появляется указателя на свою таблицу виртуальный функций и куда записывается указатель на функцию DerivedWithVirtual::bar?
  • Вопрос задан
  • 436 просмотров
Решения вопроса 1
@MiiNiPaa
стараюсь предерживаться стандарта, но вот в 11 не нашел ни слова о том как создается таблица виртуальных функций в таком случаи
С точки зрения стандарта таблицы виртуальных функций не существует, а выбирается функция магией.

Теперь насчёт вопроса. Посмотрите на более простой пример:
struct base
{
    virtual void foo() {}
};

struct derived: base
{
    virtual void bar() {}
};

std::cout << sizeof(base) << '\n'
           << sizeof(derived) << '\n'
(представляем 32битную машину и указатели по 4 байта)
4
4

То есть, при наследовании в общем случае дополнительных указателей на таблицу виртуальных функций не происходит (и это хорошо, страшно представить что бы случилось после десятка наследований). Где же хранится указатель на bar?
Ответ прост: он дописывается в единственную таблицу виртуальных функций, сразу же после указателей на функции базового класса.

Экстраполируя на вашу проблему ответ очевиден: фунцкия дописывается в одну из таблиц созданных для работы с функциями предков. Скорее всего к самой первой.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы