Зачем нужны виртуальные громы?

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

Рассмотрим следующие сценарии:

  1. класс F наследуется от класса D (и, возможно, других), который наследуется от класса B (не виртуально). D переопределяет виртуальный метод f() объявлено в Б; объект типа F создается
  2. класс F наследуется от класса D (и, возможно, других), который наследуется от класса B (фактически). D переопределяет виртуальный метод f() объявлено в Б; объект типа F создается

(единственное различие между этими двумя сценариями заключается в способе наследования класса B)

В сценарии 1 в виртуальной таблице объекта B в месте, предназначенном для f() теперь есть (невиртуальномб) что говорит:

если хочешь позвонить f()сначала поменяй this указатель с offset

(это на самом деле D, который ставит этот Thunk там)

В сценарии 2 в виртуальной таблице объекта B в месте, предназначенном для f() теперь есть (виртуальныйб) что говорит:

если хочешь позвонить f()сначала поменяй this указатель со значением, хранящимся в addr

(D не может сказать B, сколько именно this указатель должен быть скорректирован, потому что он не знает положение объекта B в окончательном макете памяти объекта F)

Эти предположения были сделаны при рассмотрении результатов g++ -fdump-class-hierarchy в комбинации с g++ -S, Они правы?

Теперь мой вопрос: почему виртуальный спасибо, надо? Почему F не может положить невиртуальном Thunk в виртуальной таблице B (в месте для f())? Потому что, когда нужно создать экземпляр объекта F, компилятор знает, что f() был объявлен в B, но он был переопределен в D. И он также знает точное смещение между объектом B (-in-F) и объектом D (-in-F) (который, я думаю, является причиной для виртуальный блин в первую очередь).

РЕДАКТИРОВАТЬ (добавлен вывод g++ -fdump-class-hierarchy а также g++ -S)

Сценарий 1:

g++ -fdump-class-hierarchy:

Vtable для F

48 (int (*) (…)) D ::_ZThn8_N1D1fEv (de-mangled: не виртуальный Thunk для D :: f ())

g++ -S:

_ZThn8_N1D1fEv:

.LFB16:

.cfi_startproc

subq $ 8,% rdi #,

jmp .LTHUNK0 #

.cfi_endproc

Сценарий 2:

g++ -fdump-class-hierarchy:

Vtable для F

64 (int (*) (…)) D ::_ZTv0_n24_N1D1fEv (de-mangled: виртуальный переход на D :: f ())

g++ -S:

_ZTv0_n24_N1D1fEv:

.LFB16:

.cfi_startproc

movq (% rdi),% r10 #,

addq -24 (% r10),% rdi #,

jmp .LTHUNK0 #

.cfi_endproc

1

Решение

Я думаю, что нашел ответ Вот:

«… Существует несколько возможных реализаций thunks с учетом приведенной выше информации. Обратите внимание на следующее, что мы предполагаем, что до вызова любой записи vtable указатель this был настроен так, чтобы указывать на подобъект, соответствующий vtable, из которого vptr извлекается.

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

B. В случае виртуального наследования смещение, хотя и известно, когда объявляется переопределитель, может отличаться в зависимости от производных от класса переопределителя. H и я выше — самый простой пример. H является первичной базой для I, но член int в I означает, что A отличается от H в I от смещения, отличного от автономного H. Из-за этого ABI указывает, что вторичная виртуальная таблица для виртуальной базы A содержит смещение vcall в H, чтобы разделяемый поток мог загрузить смещение vcall, добавив его к нему и перейти к целевой функции H :: f.
Это привело бы к меньшему количеству thunks, поскольку для иерархии наследования, где A является виртуальной базой H, а H :: f переопределяет A :: f, все экземпляры H в большей иерархии могут использовать один и тот же thunk. В результате, эти отклонения приведут к меньшему количеству неправильных прогнозов веток и кешу инструкций. Компромисс заключается в том, что они должны выполнить загрузку до добавления смещения. Поскольку смещение меньше, чем код для thunk, загрузка должна пропускаться в кеше реже, поэтому лучшее поведение при пропадании кэша должно давать лучшие результаты, несмотря на 2 или более циклов, требуемых для нагрузки смещения vcall …. «

Кажется, что виртуальный Thunk существует только из соображений производительности. Если я не прав, пожалуйста, поправьте меня.

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]