2012-06-23 3 views
3

저는 vtable 및 vpointers의 내부 동작에 대해 좀 더 배우려고 했으므로 일부 트릭을 사용하여 vtable에 직접 액세스하려고했습니다. 나는 BaseDerv의 두 클래스를 만들었는데 각각은 두 개의 virtual 함수를 가지고 있습니다 (DervBase을 오버라이드합니다).vtables 및 this 포인터

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 비트)를 차지하고, 각 클래스에 대한 포인터를 추가 VTABLE. 포인터가 크기가 sizeof(size_t) 인 다른 포인터를 가리 키므로 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(); 
} 
memberfunctions 중 하나가이 방법이라고

, 예를 들면, call(new Base(1, 2), 0)을 호출하여 Base :: foo()를 호출하면 this -pointer없이 호출되기 때문에 어떤 일이 일어날 지 예측할 수 없습니다.

template <typename T> 
void setThisPtr(T *ptr) 
{ 
    asm (mov %0, %%ecx;" :: "r" (ptr)); 
} 

은 조각에 setThisPtr(ptr) 라인의 주석 : 나는 (이 그러나 -m32 컴파일러 플래그로 컴파일 나를 강제로)를 ecx 레지스터에 저장에게 ++ 그 g을 알고는 this -pointer을 조금 템플릿 사용 기능을 추가하여 해결 지금 그것을 작동하는 프로그램을 만드는 위 : vtable을 작동하고 조금 더 나은 자료를 이해하는 다른 사람들을 도울 수있는 방법을이 작은 프로그램을 작성하는 과정에서 내가 더 통찰력을 얻은 이후

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. 64 비트 바이너리를 컴파일 할 때이 포인터를 저장하기 위해 (gcc 4.x) 사용되는 레지스터는 어느 것입니까? 여기에 설명 된대로 모든 64 비트 레지스터를 시도했습니다 : http://developers.sun.com/solaris/articles/asmregs.html
2. this 포인터가/when은 어떻게 설정됩니까? 필자는 컴파일러가 각 함수 호출에서이 포인터를 객체를 통해 비슷한 방법으로 설정했다고 의심합니다. 이것은 다형성이 실제로 작용하는 방식입니까? (이 포인터를 먼저 설정 한 다음 vtable에서 가상 함수를 호출하면됩니까?).

+0

이것은 일반적인 SO 질문에 대해 약간 길다.이 문제를 핵심 쟁점으로 요약 할 수 있습니까? (실제로 최종 질문과 직접적으로 관련이있는 게시물은 무엇입니까?) –

+0

@Oli Charlesworth 글쎄, 내가이 질문에 온 것이기 때문에 그것은 나를위한 것이다. 두 번째 질문은 컴파일러가 다형성을 가능하게하는 비슷한 방법론을 사용하는지 묻습니다. 그래서 제 자신을 포함시켜야한다고 생각합니다. 이 정보를 공유하고 질문 할 수있는 다른 매체를 제안 하시겠습니까? – JorenHeit

+0

당신은 여기에 질문을해야하며, 그 질문에 대해 충분한 컨텍스트를 가지고 이해해야합니다 (이러한 특정 질문에 대한 * 모든 컨텍스트가 필요하지는 않습니다.). 당신이 발견 한 것을 공유하고 싶다면 블로그를 설정해야합니다. –

답변

4

Linux x86_64에서 나는 다른 UNIX 계열 OS를 믿고 있습니다. 함수 호출은 System V ABI (AMD64)을 따르며, 그 자체는 IA-64 C++ ABI (C++)을 따릅니다. 메소드의 타입에 따라 this 포인터는 첫 번째 인수 또는 두 번째 인수를 통해 암시 적으로 전달됩니다 (반환 값에 중요하지 않은 복사 생성자 또는 소멸자가있는 경우 스택에 임시로 존재해야하며 첫 번째 인수는 암시 적으로 a입니다. 그 공간에 대한 포인터); 그렇지 않으면, 가상 메소드 호출 C에 통화 기능과 동일하다 (; %rax의 정수/포인터 리턴; %xmm0 수레 - %xmm7; %rdi%rsi%rdx%rcx%r8%r9, 스택 오버 플로우가있는 정수/포인터 인수 등) . 가상 메소드 디스패치는 vtable에서 포인터를 검색 한 다음 가상 메소드가 아닌 것처럼 호출하여 작동합니다.

필자는 Windows x64 규칙에 익숙하지 않지만 C++ 메서드 호출이 C 함수 호출 (Linux와 다른 레지스터를 사용함)과 똑같은 구조를 따르고 있다고 가정합니다. 암시적인 this 먼저 인수.

+0

Microsoft 호환 C++ 구현, vtable은 Don Box의 Essential COM & (COM 내 다른 책에 대해 들었습니다.)의 입문 부분에서 설명합니다. –