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;
}