C++代码如下:
class parent_parent { public: virtual int print() const { return 1; } }; class sub : public parent_parent { public: int print() const override { return 0; } }; int main() { parent_parent* p = new sub; p->print(); delete p; return 0; }
通过使用反汇编可得到main
函数如下:
main: .LFB2: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 pushq %rbx subq $24, %rsp .cfi_offset 3, -24 movl $8, %edi call _Znwm@PLT # 此处调用operator new(unsigned long)@PLT分配内存 movq %rax, %rbx # 保存new的返回值到rbx寄存器 movq %rbx, %rdi # 复制返回值到rdi寄存器 call _ZN3subC1Ev # 此处调用sub::sub()构造函数 movq %rbx, -24(%rbp) movq -24(%rbp), %rax movq (%rax), %rax movq (%rax), %rdx # 将print函数指针移动至rdx movq -24(%rbp), %rax movq %rax, %rdi call *%rdx # 此处调用p->print()函数 movq -24(%rbp), %rax testq %rax, %rax je .L8 movl $8, %esi movq %rax, %rdi call _ZdlPvm@PLT .L8: movl $0, %eax addq $24, %rsp popq %rbx popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc
parent_parent构造函数如下:
_ZN13parent_parentC2Ev: .LFB5: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq %rdi, -8(%rbp) leaq 16+_ZTV13parent_parent(%rip), %rdx # 加载虚表地址 + rip地址 + 16,即parent_parent::print()函数的地址,放入rdx寄存器 movq -8(%rbp), %rax movq %rdx, (%rax) # 放入rax内地址指向的内存,即new分配的内存 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc
以及sub构造函数如下:
_ZN3subC2Ev: .LFB7: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movq %rdi, -8(%rbp) movq -8(%rbp), %rax movq %rax, %rdi # 保存new地址 call _ZN13parent_parentC2Ev #调用parent_parent::parent_parent() leaq 16+_ZTV3sub(%rip), %rdx # 加载虚表地址 + rip地址 + 16,sub::print()函数的地址,放入rdx寄存器 movq -8(%rbp), %rax movq %rdx, (%rax) # 放入rax内地址指向的内存,即new分配的内存 nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc
调用过程大致如下:
先调用new函数分配内存,然后调用父类构造器,获取父类虚函数地址。然后,查询子类虚表,覆盖父类函数。调用子类override的函数
虚表构成:
vtable of parent_parent:
vtable for parent_parent: .quad 0 .quad typeinfo for parent_parent # 类型信息地址 .quad parent_parent::print() const # print函数地址 .weak typeinfo for sub .section .data.rel.ro._ZTI3sub,"awG",@progbits,typeinfo for sub,comdat .align 8 .type typeinfo for sub, @object .size typeinfo for sub, 24
vtable for sub:
vtable for sub: .quad 0 .quad typeinfo for sub .quad sub::print() const .weak vtable for parent_parent .section .data.rel.ro.local._ZTV13parent_parent,"awG",@progbits,vtable for parent_parent,comdat .align 8 .type vtable for parent_parent, @object .size vtable for parent_parent, 24
所以根据以上原理,可以通过如下方式调用虚表中的函数:
#include <iostream> using namespace std; class subclass { public: virtual void print() { cout << "hello world !\n"; } }; int main() { subclass* sub = new subclass; // sub 指向的地址存放 vtable存放函数的地址 void* vtable16 = *(void**)sub; // 通过存放函数地址的地址取得函数地址 void* funcs = *(void**)vtable16; // 获取print函数的地址 auto print_ptr = (void(*)())(funcs); // 通过函数地址调用函数 print_ptr(); delete sub; return 0; }