TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #967 4.44K
Современные таблицы виртуальных функций
#опытным

С++ давно ушел от компиляции в С. Но таблицы виртуальных функций, как средство реализации динамического полиморфизма, остались в компиляторах.

Как устроены vtables сейчас, когда С++ напрямую компилируется в ассемблер?

Идея все та же:

👉🏿 На каждый полиморфный класс создается своя vtable

👉🏿 Она представляет собой просто массив адресов. В ассемблере адреса нетипизированные, поэтому они просто лежат там чиселками.

👉🏿 В каждом объекте хранится vptr - указатель на эту таблицу.

👉🏿 vtpr инициализируется в самом базовом классе его адресом его vtable и затем каждый конструктор наследника присваивает в него адрес своей таблицы

Реализации вносят свои особенности, но это ядро остается неизменным.

Вот примерчик(можете смотреть на годболте):

class Person {
protected:
std::string name;
int age;
public:
Person(const std::string &n, int a) : name(n), age(a) {}
virtual std::string getRole() const { return "Person"; }
virtual std::string getName() const { return name; }
virtual void describe() const {
std::cout << name << " (" << age << " years)";
}
virtual ~Person() = default;
};

class Employee : public Person {
std::string position;
public:
Employee(const std::string &n, int a, const std::string &pos)
: Person(n, a), position(pos) {}
std::string getRole() const override { return "Employee"; }
void describe() const override {
std::cout << name << " (" << age << " years) - " << position;
}
};

int main() {
Person * p = new Employee("Steven", 42, "CEO");
p->describe();
std::cout << p->getName() << std::endl;
}


Будем сейчас разбирать gcc-шный асм.

Вот так выглядят таблицы для обоих классов:

vtable for Person:
.quad 0
.quad typeinfo for Person
.quad Person::getRoleabi:cxx11 const
.quad Person::getNameabi:cxx11 const
.quad Person::describe() const
.quad Person::~Person()

vtable for Employee:
.quad 0
.quad typeinfo for Employee
.quad Employee::getRoleabi:cxx11 const
.quad Person::getNameabi:cxx11 const
.quad Employee::describe() const
.quad Employee::~Employee()


quad - это 64-битное число. Видно, что таблицы - это статические массивы.

Первым числом у них является offset to top, это число нужно для корректной работы множественного наследования, не будем вдаваться в детали.

Второй число - адрес расположения информации о динамическом типе(RTTI) объекта. Эта информация нужна, например, для dynamic_cast'а.

Дальше расположены адреса виртуальных функций нужных классов. Заметьте, что адреса расположены в порядке объявления виртуального метода в классе. Если в дочернем классе переопределяется метод, то в его vtable указатель родительского метода заменяется на переопределенный(методы getRole и describe). Если наследник не переопределяет метод, то в таблице остается указатель на родительский метод(getName).

В конструкторе Employee происходит такое:

; Сначала вызывается конструктор Person (устанавливает vptr Person)
call Person::Person(...) [base object constructor]

; Затем перезаписывается vptr на таблицу Employee
mov edx, OFFSET FLAT:vtable for Employee+16
mov rax, QWORD PTR [rbp-24]
mov QWORD PTR [rax], rdx


Вызывается конструктор базового класса и сразу же после этого переприсваивается vptr на таблицу класса Employee.

Ну а виртуальный вызов выглядит просто как call нужного адреса:

mov     rax, QWORD PTR [rbp-40]    ; Загружаем указатель p
mov rax, QWORD PTR [rax] ; Загружаем vptr (указывает на vtable+16)
add rax, 16 ; Смещаемся к 4-му слоту (describe)
mov rdx, QWORD PTR [rax] ; Загружаем адрес функции describe
mov rax, QWORD PTR [rbp-40] ; Загружаем this (указатель p)
mov rdi, rax ; Передаем this как первый параметр
call rdx ; Виртуальный вызов describe
  • ❤‍🔥 24
  • ❤ 13
  • 👍 6
  • 🔥 6
  • 😁 2
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →