2017-09-12 3 views
4

C++ではvftableを使用して、どの仮想関数を呼び出すべきかを動的に決定することが知られています。私は仮想関数を呼び出すとき、その背後にあるメカニズムを見つけたいと思います。私はアセンブリに次のコードをコンパイルしました。vftable [0]は、最初の仮想関数またはRTTI Complete Object Locatorを格納していますか?

using namespace std; 

class Animal { 
    int age; 
public: 
    virtual void speak() {} 
    virtual void wash() {} 
}; 

class Cat : public Animal { 
public: 
    virtual void speak() {} 
    virtual void wash() {} 
}; 

void main() 
{ 
    Animal* animal = new Cat; 
    animal->speak(); 
    animal->wash(); 
} 

アセンブリコードは大量です。私は次の部分をかなり理解していません。

CONST SEGMENT 
[email protected]@[email protected] DD FLAT:[email protected]@[email protected]   ; Cat::`vftable' 
    DD FLAT:[email protected]@@UAEXXZ 
    DD FLAT:[email protected]@@UAEXXZ 
CONST ENDS 

この部分は、Catのvftableを定義します。しかし、それは3つのエントリがあります。最初のエントリはRTTI Complete Object Locatorです。 2番目はCat :: speakです。 3番目はCat :: washです。だから、vftable [0]はRTTI Complete Object Locatorを暗示すべきだと思います。しかし、メインのPROCとCat :: Cat PROCのアセンブリコードをチェックすると、animal->speak()への呼び出しはvftable [0]を呼び出して実装され、animal->wash()への呼び出しはvftable [4]を呼び出すことによって実装されます。どうしてvftable [4]とvftable [8]なのでしょうか?

PROC mainとCat :: Catのアセンブリコードが以下に示されています。

_TEXT SEGMENT 
tv75 = -12      ; size = 4 
$T1 = -8      ; size = 4 
_animal$ = -4      ; size = 4 
_main PROC 

; 23 : { 

    push ebp 
    mov ebp, esp 
    sub esp, 12     ; 0000000cH 

; 24 : Animal* animal = new Cat; 

    push 8 
    call [email protected]@Z    ; operator new 
    add esp, 4 
    mov DWORD PTR $T1[ebp], eax 
    cmp DWORD PTR $T1[ebp], 0 
    je SHORT [email protected] 
    mov ecx, DWORD PTR $T1[ebp] 
    call [email protected]@[email protected] 
    mov DWORD PTR tv75[ebp], eax 
    jmp SHORT [email protected] 
[email protected]: 
    mov DWORD PTR tv75[ebp], 0 
[email protected]: 
    mov eax, DWORD PTR tv75[ebp] 
    mov DWORD PTR _animal$[ebp], eax 

; 25 : animal->speak(); 

    mov ecx, DWORD PTR _animal$[ebp] 
    mov edx, DWORD PTR [ecx] 
    mov ecx, DWORD PTR _animal$[ebp] 
    mov eax, DWORD PTR [edx] 
    call eax 

; 26 : animal->wash(); 

    mov ecx, DWORD PTR _animal$[ebp] 
    mov edx, DWORD PTR [ecx] 
    mov ecx, DWORD PTR _animal$[ebp] 
    mov eax, DWORD PTR [edx+4] 
    call eax 

; 27 : } 

    xor eax, eax 
    mov esp, ebp 
    pop ebp 
    ret 0 
_main ENDP 
_TEXT ENDS 

; Function compile flags: /Odtp 
; COMDAT [email protected]@[email protected] 
_TEXT SEGMENT 
_this$ = -4      ; size = 4 
[email protected]@[email protected] PROC     ; Cat::Cat, COMDAT 
; _this$ = ecx 
    push ebp 
    mov ebp, esp 
    push ecx 
    mov DWORD PTR _this$[ebp], ecx 
    mov ecx, DWORD PTR _this$[ebp] 
    call [email protected]@[email protected] 
    mov eax, DWORD PTR _this$[ebp] 
    mov DWORD PTR [eax], OFFSET [email protected]@[email protected] 
    mov eax, DWORD PTR _this$[ebp] 
    mov esp, ebp 
    pop ebp 
    ret 0 
[email protected]@[email protected] ENDP     ; Cat::Cat 
_TEXT ENDS 

補足:MSVCコンパイラのx86 19.00.23026

+0

最後に私が確認したところでは、MSVCは状況に応じて異なるvtableレイアウトを生成します。チェックアウトしたい場合があります –

答えて

3

のvtableのレイアウトは実装依存です。具体的には、サンプルコードをコンパイルするとき、Microsoft C++コンパイラはspeak仮想関数がオフセット0にあり、wash関数がオフセット4にあるCatのvtableを生成します。RTTI情報は、 4。

ここで問題となるのは、マイクロソフトのアセンブリ出力が横たわっていることです。生成されたアセンブリコードはRTTI情報をオフセット0に置き、speakwashはオフセット4と8で機能します。しかし、これは実際にコンパイラが生成するオブジェクトファイルにどのようにレイアウトされているかは分かりません。オブジェクトファイルを逆アセンブルすることは、このレイアウトを明らかにする:

   .new_section .rdata, "dr2" 
0000 00 00 00 00          .long [email protected]@[email protected] 
0004       [email protected]@[email protected]: 
0004 00 00 00 00          .long [email protected]@@UAEXXZ 
0008 00 00 00 00          .long [email protected]@@UAEXXZ 

は、残念ながら、MicrosoftのCのアセンブリ出力は/ C++コンパイラは、情報提供のみであることを意味します。これはコンパイラが生成する実際のコードの正確で完全な表現ではありません。特に、作業オブジェクトファイルに確実に組み込むことはできません。

関連する問題