编程语言
首页 > 编程语言> > C++虚函数调用简单分析

C++虚函数调用简单分析

作者:互联网

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

标签:sub,parent,cfi,C++,rbp,函数调用,简单,rax,movq
来源: https://www.cnblogs.com/ug-nan/p/15925696.html