Я пытался узнать больше о внутренней работе vtables и vpointers, поэтому я решил попробовать получить доступ к vtable напрямую, используя некоторые трюки. Я создал два класса: Base
и Derv
, каждый из которых имеет две функции virtual
(Derv
, переопределяя их Base
).vtables и этот указатель
class Base
{
int x;
int y;
public:
Base(int x_, int y_) : x(x_), y(y_) {}
virtual void foo() { cout << "Base::foo(): x = " << x << '\n'; }
virtual void bar() { cout << "Base::bar(): y = " << y << '\n'; }
};
class Derv: public Base
{
int x;
int y;
public:
Derv(int x_, int y_) : Base(x_, y_), x(x_), y(y_) {}
virtual void foo() { cout << "Derived::foo(): x = " << x << '\n'; }
virtual void bar() { cout << "Derived::bar(): y = " << y << '\n'; }
};
Теперь компилятор добавляет указатель на таблицу виртуальных каждого класса, занимая первые 4 байта (32 бита) в памяти. Я обратился к этому указателю, поместив адрес объекта в size_t*
, так как указатель указывает на другой указатель размера sizeof(size_t)
. Теперь к виртуальным функциям можно получить доступ, указав vpointer и выведя результат в указатель функции соответствующего типа. Я заключил эти шаги в функцию:
template <typename T>
void call(T *ptr, size_t num)
{
typedef void (*FunPtr)();
size_t *vptr = *reinterpret_cast<size_t**>(ptr);
FunPtr fun = reinterpret_cast<FunPtr>(vptr[num]);
//setThisPtr(ptr); added later, see below!
fun();
}
Когда одна из функций-членов называется таким образом, например. call(new Base(1, 2), 0)
, чтобы вызвать Base :: foo(), трудно предсказать, что произойдет, поскольку они вызываются без this
-поинтер. Я решил, добавив немного Шаблонные функции, зная, что G ++ хранит в this
-указатель в ecx
регистре (это, однако, заставляет меня компилировать с флагом в -m32
компилятор):
template <typename T>
void setThisPtr(T *ptr)
{
asm (mov %0, %%ecx;" :: "r" (ptr));
}
раскомментирован setThisPtr(ptr)
линию во фрагменте выше в настоящее время делает его рабочую программу:
int main()
{
Base* base = new Base(1, 2);
Base* derv = new Derv(3, 4);
call(base, 0); // "Base::foo(): x = 1"
call(base, 1); // "Base::bar(): y = 2"
call(derv, 0); // "Derv::foo(): x = 3"
call(derv, 1); // "Derv::bar(): y = 4"
}
я решил поделиться этим, так как в процессе написания этой небольшой программы, я получил больше понимания в том, как виртуальные таблицы работать, и это может помочь другим понять этот материал немного лучше , Однако у меня остались вопросы:
1. Какой регистр используется (gcc 4.x) для хранения этого указателя при компиляции 64-битного двоичного кода? Я пробовал все 64-разрядные регистры, как описано здесь: http://developers.sun.com/solaris/articles/asmregs.html
2. Когда/как этот набор указателей? Я подозреваю, что компилятор устанавливает этот указатель на каждый вызов функции через объект аналогично тому, как я это сделал. Так ли работает полиморфизм? (Сначала указав этот указатель, а затем вызовите виртуальную функцию из таблицы vtable?).
Это немного длинно для типичного вопроса SO.Можете ли вы сконцентрироваться на этом вопросе? (На самом деле, какое-либо ваше сообщение имеет непосредственное отношение к вопросам в конце?) –
@ Oli Charlesworth Ну, это для меня, так как именно так я пришел к этим вопросам. Второй вопрос спрашивает, использует ли компилятор аналогичную методологию для полиморфизма, поэтому я думаю, что я должен включить свою собственную. Не могли бы вы предложить другую среду, через которую я мог бы поделиться этой информацией и задать вопросы? – JorenHeit
Здесь вы должны задать вопросы, имея достаточно контекста, чтобы они имели смысл (это не похоже на какой-либо * контекст для этих конкретных вопросов). Если вы хотите поделиться тем, что вы обнаружили, вероятно, вы должны создать блог ... –