Страницы

Поиск по вопросам

четверг, 19 декабря 2019 г.

“Thunk” в контексте виртуальных функций

#cpp #терминология


Что такое "Thunk" в контексте виртуальных функций?(Как работает?)
    


Ответы

Ответ 1



В некоторых реализациях термин "thunk" означает "промежуточную вызывалку" для виртуальной функции. "Промежуточная вызывалка" может быть нужна тогда, когда для правильного вызова виртуального метода необходимо предварительно вычислить корректное значение указателя this, но способ этого вычисления зависит от контекста времени выполнения. Thunk также может использоваться для реализации корректной функциональности указателей на методы в ситуациях, когда такие указатели указывают на виртуальные методы. Например #include struct Base { virtual void foo() { std::cout << "base" << std::endl; } }; struct Derived : Base { virtual void foo() { std::cout << "derived" << std::endl; } }; int main() { void (Base::*ptr)() = &Base::foo; // 1 Base b; (b.*ptr)(); // 2 Derived d; (d.*ptr)(); // 3 } В этом коде указатель ptr вроде бы "привязывается" к функции Base::foo в точке 1. Однако в ситуациях, когда Base::foo является виртуальной функцией, спецификация языка требует, чтобы решение о конкретной функции для вызова принималось не в точке инициализации (точка 1), а в точке вызова (то есть в точках 2 и 3) на основе анализа динамического типа объекта. В точке 2 должна быть вызвана Base::foo, а в точке 3 - Derived::foo. Поэтому компилятор не может в точке 1 заставить указатель ptr указывать напрямую на Base::foo. Он вынужден использовать какой-то промежуточный код, который уже на основе анализа динамического типа объекта в точках 2 и 3 примет решение о том, какую же функцию надо вызвать в этом месте. Одним из вариантов реализации такого механизма является заведение в классе Base скрытого невиртуального метода-"вызывалки", который внутри себя делает обычный виртуальный вызов метода foo. Указатель ptr ставится именно на этот скрытый метод struct Base { virtual void foo() { std::cout << "base" << std::endl; } void foo_thunk() { foo(); } }; ... void (Base::*ptr)() = &Base::foo; // на самом деле транслируется в void (Base::*ptr)() = &Base::foo_thunk; В результате мы получаем требуемое поведение при вызовах через ptr в точках 2 и 3. Т.е. в обоих случаях сначала вызывается именно Base::foo_thunk, а уже оттуда через обычный виртуальный механизм управление попадает в правильное foo. Вот этот скрытый метод foo_thunk - это и есть так называемый "thunk". Таким способом реализует вызовы через указатели на виртуальные методы компилятор MSVC++. Компилятор GCC при компиляции вышеприведенного простейшего примера будет использовать иной подход (без thunk), но стоит ввести в пример множественное наследование, как в сгенерированном GCC коде тоже появятся thunks. В том числе именно для поддержки такого подхода к реализации указателей на методы стандарт языка С++ говорит, что результат сравнения двух указателей на методы не специфицирован, если хотя бы один из них указывает на виртуальный метод. Дело в том, что на низком уровне один и тот же thunk может переиспользоваться для обслуживания совершенно разных виртуальных методов. Т.е. устанавливая указатель на разные виртуальные методы вы можете тем не менее получить одно и то же физическое значение в указателе (указатель на один и тот же thunk).

Комментариев нет:

Отправить комментарий