Методы являются функциями, но указатели на методы обычно не являются указателями на функции.
Соглашение о вызове методов вызова не всегда согласуется с соглашением о вызове функций.
Мы можем обойти это. С еще более неопределенным поведением, но это работает, по крайней мере, иногда.
MSVC лязг г ++
Код:
template<class Sig>
struct fake_it;
template<class R, class...Args>
struct fake_it<R(Args...)>{
R method(Args...);
using mptr = decltype(&fake_it::method);
};
template<class R, class...Args>
struct fake_it<R(Args...) const> {
R method(Args...) const;
using mptr = decltype(&fake_it::method);
};
template<class Sig>
using method_ptr = typename fake_it<Sig>::mptr;
template<class Sig>
struct this_helper {
using type=fake_it<Sig>*;
};
template<class Sig>
struct this_helper<Sig const>{
using type=fake_it<Sig> const*;
};
template<class Sig>
using this_ptr = typename this_helper<Sig>::type;
теперь этот тестовый код:
Car car;
void* carPtr = &car;
auto **mVtable = (uintptr_t **)(carPtr);
printf("VTable: %p\n", *mVtable);
printf("First Entry of VTable: %p\n", (void*)mVtable[0][0]);
printf("Second Entry of VTable: %p\n", (void*)mVtable[0][1]);
if(sizeof(void*) == 8){
printf("64 bit\n");
}
auto firstfunc = to_method_ptr<int()>(mVtable[0][0]);
int x = (this_ptr<int()>(carPtr)->*firstfunc)();
auto secondfunc = to_method_ptr<int()>(mVtable[0][1]);
int x2 = (this_ptr<int()>(carPtr)->*secondfunc)();
printf("first: %d\nsecond: %d", x, x2);
Приведенный выше код основывается на указателях методов, представляющих собой пару указателей на функции и второй раздел, в котором, если все 0 - не виртуальная диспетчеризация, и в таблице, содержащей только указатель на функцию.
Таким образом, мы можем восстановить указатель метода из данных в vtable, заполнив буфер 0, а затем интерпретировать память как указатель метода.
Чтобы заставить вызов работать, мы создаем фальшивый тип с методом, который соответствует нашей сигнатуре, затем приводим наш указатель на этот тип и вызываем его с указателем на функцию-член, восстановленную из vtable нашего исходного типа.
Это, мы надеемся, имитирует соглашение о вызовах, которое компилятор использует для других вызовов методов.
В clang / g ++ указатели не виртуальных методов - это два указателя, второй игнорируется. Я полагаю, что указатели виртуальных методов используют данные второго размера.
В MSVC указатели не виртуальных методов имеют размер одного указателя. Указатели виртуальных методов с виртуальным деревом наследования не имеют размер одного указателя. Я считаю, что это нарушает стандарт (который требует, чтобы указатели на элементы были взаимозаменяемыми между собой).
В обоих случаях vtable хранит первую половину каждого указателя не виртуального метода.