2017-09-12 17 views
4

我们知道C++使用vftable来动态地决定应该调用哪个虚函数。当我们调用虚函数时,我想找出它背后的机制。我编译了下面的代码进行汇编。vftable [0]是否存储第一个虚拟函数或RTTI完整对象定位器?

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 

这部分定义了猫的vftable。但它有三个条目。第一个条目是RTTI完整对象定位器。第二个是Cat :: speak。第三个是Cat :: wash。所以我认为vftable [0]应该暗示RTTI完全对象定位器。但是,当我检查主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编译器86 19.00.23026

+0

最后我检查了一下,MSVC根据情况生成不同的vtable布局,你可能想查看它 –

回答

3

虚函数表的布局是依赖于实现。在编写示例代码时,Microsoft C++编译器会生成一个用于Cat的虚拟表,其中speak虚拟函数位于偏移量0,而wash函数位于偏移量4处。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 

不幸的是微软的C的汇编输出/ C++编译器是指只待信息。这不是编译器生成的实际代码的准确完整表示。特别是它不能可靠地组装成工作对象文件。

相关问题