2012-06-23 3 views
3

Я пытался узнать больше о внутренней работе 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?).

+0

Это немного длинно для типичного вопроса SO.Можете ли вы сконцентрироваться на этом вопросе? (На самом деле, какое-либо ваше сообщение имеет непосредственное отношение к вопросам в конце?) –

+0

@ Oli Charlesworth Ну, это для меня, так как именно так я пришел к этим вопросам. Второй вопрос спрашивает, использует ли компилятор аналогичную методологию для полиморфизма, поэтому я думаю, что я должен включить свою собственную. Не могли бы вы предложить другую среду, через которую я мог бы поделиться этой информацией и задать вопросы? – JorenHeit

+0

Здесь вы должны задать вопросы, имея достаточно контекста, чтобы они имели смысл (это не похоже на какой-либо * контекст для этих конкретных вопросов). Если вы хотите поделиться тем, что вы обнаружили, вероятно, вы должны создать блог ... –

ответ

4

В Linux x86_64, и я считаю, что другие UNIX-подобные операционные системы, вызовы функций следуют за System V ABI (AMD64), который сам следует за IA-64 C++ ABI для C++. В зависимости от типа метода указатель this либо передается неявно через первый аргумент, либо второй аргумент (когда возвращаемое значение имеет нетривиальный конструктор или деструктор копии, он должен находиться в качестве временного стека, а первый аргумент неявно указатель на это пространство); в противном случае, виртуальные вызовы методов идентичны вызовы функций в С (аргументами целого числа/указателем в %rdi, %rsi, %rdx, %rcx, %r8, %r9, перелив в стек; целое числе/указатель возврата в %rax; плавает в %xmm0 - %xmm7; и т.д.) , Диспетчер виртуальных методов работает, просматривая указатель в vtable, а затем вызывает его как не виртуальный метод.

Я менее знаком с соглашениями Windows x64, но я считаю, что это похоже на то, что вызовы метода C++ соответствуют той же структуре, что и вызовы функций C (которые используют разные регистры, чем в Linux), только с неявным this сначала аргумент.

+0

Для совместимых с Microsoft C++ реализация, vtable объясняется во вводной части Essential COM COM от Don Box (я слышал о другой книге Inside COM) –

Смежные вопросы