Где хранится таблица виртуальных методов в C++?

Я хочу знать, как объект класса (не экземпляры, а именно классы) хранится в памяти?

class A {
public:
    int a;
    virtual void f();
    virtual ~A();
};

class B : public A {
public:
    int b;
    void f() final override;
};

Я знаю, что обычно (не строго описано стандартом) в случае этого наследования (B, полученного из A) мы имеем:

memory: ....AB...

где AB - объект класса B (если я правильно понимаю). Если мы пойдем глубже (пробовал с clang и gcc), мы можем увидеть что-то вроде (опять же, не сильно описано в стандарте):

A
    vtptr*
    int a
B
    vtptr*
    int b

Хорошо, теперь мы видим, где a а также b магазин недвижимости. И мы также видим указатель на таблицу виртуальных методов. Но где же vtptr* (таблица виртуальных методов) на самом деле хранить? Почему не рядом с классами? Или это так?

Кроме того, вот еще один вопрос: я смог изменить таблицы виртуальных методов, изменив указатели (простая логика). Могу ли я также безопасно изменить указатель на его методы?

PS На ваши вопросы вы можете ответить за gcc и clang. PPS Если я где-то ошибаюсь, укажите это тоже в своих ответах.

3 ответа

Стандарт C++ не предписывает, как механизм виртуальной функции должен быть реализован. На практике все реализации C++ используют таблицу виртуальных функций для каждого класса и указатель таблицы виртуальных функций в каждом объекте класса с виртуальными функциями (называемыми полиморфным классом). Все же детали могут отличаться, в частности для множественного наследования и виртуального наследования.

Вы можете прочитать об общих решениях в классической книге Стэнли Липпмана " Внутри объектной модели C++".

Нет смысла спрашивать "где" хранится таблица виртуальных функций. Это очень похоже на любую статическую переменную: ее расположение зависит от реализации и в значительной степени произвольно. И относительно

Почему не рядом с классами?

... классы как таковые нигде не хранятся, они не являются объектами, так что это не имеет смысла, извините.

Вы можете спросить более осмысленно, где указатель vtable хранится в каждом объекте для данной реализации?

И обычно это в начале объекта, но если вы наследуете неполиморфный класс и добавляете виртуальную функцию, вы можете получить указатель vtable где-нибудь еще. Или нет. Последняя возможность во многом является причиной, почему static_cast из Derived* в Base* (или наоборот) может сделать настройку адреса, т.е. отличается от простого reinterpret_cast,

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

Где хранится vtable (сам) зависит от реализации (компилятор, компоновщик, операционная система). Но он часто хранится (как буквальные строки) в сегменте кода вашего исполняемого файла. Таким образом, объекты обычно (т.е. без множественного наследования) начинаются с _vptr указатель, указывающий на их vtable. При множественном или виртуальном наследовании вы можете иметь несколько указателей vtable.

Как прокомментировано, вы не должны заботиться об этих деталях. Если вам действительно важно, попросите ваш компилятор вывести внутренние представления или испущенный код сборки. (например, скомпилировать с g++ -fdump-tree-all -fverbose-asm -S)

Но где на самом деле vtptr* (таблица виртуальных методов) [точка]? Почему не рядом с классами? Или это так?

Это может быть где угодно... кого это волнует? То, как это работает, часто реализуется, примерно так... представьте, что есть скрытый static член для class A:

VDT A::vdt = {
    { address of A::f code, 
      address of A::~A code },
    miscellaneous type-specific information needed for dynamic cast etc.
};

Точное расположение неизвестно, но вполне может быть массив адресов virtual функции-члены. Как с любым static информация, адрес не связан с адресом любого данного экземпляра объекта... указатели в объектах на виртуальную таблицу диспетчеризации существуют для того, чтобы разрешить это разъединение.

Кроме того, вот еще один вопрос: я смог изменить таблицы виртуальных методов, изменив указатели (простая логика). Могу ли я также безопасно изменить указатель на его методы?

Это небезопасно, и даже если это якобы работает, иногда это может не соблюдаться последовательно (например, в ситуациях, когда компилятор может определить конкретное переопределение для вызова во время компиляции, он может полностью обойти консультацию виртуальной таблицы диспетчеризации во время выполнения).

Другие вопросы по тегам