首页 > 其他分享 >虚函数

虚函数

时间:2023-07-11 16:36:42浏览次数:54  
标签:draw 函数 16 rdx rbp rax movq

虚函数

虚函数表

示例

// code of virtual function
// filename: test.cpp
#include <stdio.h>

class A
{
public:
	virtual ~A() {}
	void draw(){draw_imp();}
protected:
	virtual void draw_imp(){}
};

class B : public A
{
public:
protected:
	void draw_imp() override{}
};

int main()
{
	A *a = new B();
	a->draw();
	delete a;
	return 0;
}
g++ test.cpp --dump-lang-class
cat a-*

得到信息为

Vtable for A
A::_ZTV1A: 5 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1A)
16    (int (*)(...))A::~A
24    (int (*)(...))A::~A
32    (int (*)(...))A::draw_imp

Class A
   size=8 align=8
   base size=8 base align=8
A (0x0x7f223519eb40) 0 nearly-empty
    vptr=((& A::_ZTV1A) + 16)

Vtable for B
B::_ZTV1B: 5 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1B)
16    (int (*)(...))B::~B
24    (int (*)(...))B::~B
32    (int (*)(...))B::draw_imp

Class B
   size=8 align=8
   base size=8 base align=8
B (0x0x7f2235043270) 0 nearly-empty
    vptr=((& B::_ZTV1B) + 16)
A (0x0x7f223519ef00) 0 nearly-empty
      primary-for B (0x0x7f2235043270)

可以看到,两个存在继承关系的类,其虚函数表中的函数(析构函数和draw)分别为各自类的实现;

注:虚函数表中一些特殊的结构,比如16字节偏移、对齐、两个析构函数,这些都是ABI的要求,不必关注;

虚函数的原理

汇编分析

g++ test.cpp -S
cat test.s

摘录其中一部分分析虚函数的调用过程(移除了大部分特殊指示代码):


_ZN1A4drawEv:                   # A::draw()
    pushq	%rbp                # 栈基址
    movq	%rsp, %rbp          # 新的栈基址
    subq	$16, %rsp   
	subq	$16, %rsp           # 栈共增长32Byte
	movq	%rdi, -8(%rbp)      # this --> 栈第一个8字节
	movq	-8(%rbp), %rax      # this --> rax
	movq	(%rax), %rax        # *rax --> rax :this的首部就是虚函数表的指针地址,8字节)
	addq	$16, %rax           # rax偏移16字节,考虑到虚表本身偏移16,即总共偏移为32,指向draw_imp
	movq	(%rax), %rdx        # *rax --> rax :获取函数地址(draw_imp)
	movq	-8(%rbp), %rax      # 
	movq	%rax, %rdi
	call	*%rdx               # 调用(draw_imp)
	nop
	leave
	ret

_ZN1AC2Ev:                          # A::A()
	endbr64
	pushq	%rbp
	movq	%rsp, %rbp
	movq	%rdi, -8(%rbp)          # this指针放到栈的第一个8字节
	leaq	16+_ZTV1A(%rip), %rdx   # rdx = 虚表首部地址 + 16
	movq	-8(%rbp), %rax          # rax = this
	movq	%rdx, (%rax)            # *this = rdx ,即类A的虚表指针放到对象的第一个8字节
	nop
	popq	%rbp
	ret
.LFE10:
	.set	_ZN1AC1Ev,_ZN1AC2Ev

_ZN1BC2Ev:                      # B::B()
	endbr64
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)      # this指针放到栈的第一个8字节
	movq	-8(%rbp), %rax      # rax = this
	movq	%rax, %rdi          # rdi = this
	call	_ZN1AC2Ev           # A::A()
	leaq	16+_ZTV1B(%rip), %rdx   # 类B的虚表指针存放到rdx
	movq	-8(%rbp), %rax      # rax = this
	movq	%rdx, (%rax)        # 类B的虚表指针存放到this对象的第一个8字节(覆盖了A产生的虚表指针)
	nop
	leave
	ret
.LFE12:
	.set	_ZN1BC1Ev,_ZN1BC2Ev

main:
	endbr64
	pushq	%rbp                # 栈基址
	movq	%rsp, %rbp          # 新的栈基址
	pushq	%rbx                # 
	subq	$24, %rsp           # 栈增长24字节
	movl	$8, %edi            # edi = 8
	call	_Znwm@PLT           # call new (size = 8)
	movq	%rax, %rbx          # rax --> rbx(new得到的指针)
	movq	$0, (%rbx)          # *rbx = 0
	movq	%rbx, %rdi          # rbx --> rdi
	call	_ZN1BC1Ev           # call B::B()
	movq	%rbx, -24(%rbp)     # rbx 放到栈的第一个8字节(new得到的指针)
	movq	-24(%rbp), %rax     # rax = (new得到的指针)
	movq	%rax, %rdi          # rdx = (new得到的指针)
	call	_ZN1A4drawEv        # call A::draw()
	movq	-24(%rbp), %rax     # rax = (new得到的指针)
	testq	%rax, %rax
	movq	(%rax), %rdx        # rdx = 虚表指针(B的虚表),vtable + 16
	addq	$8, %rdx            # rdx = vtable + 24
	movq	(%rdx), %rdx        # rdx = *(vtable + 24), i.e. B::~B()
	movq	%rax, %rdi          
	call	*%rdx               # call B::~B()
	movl	$0, %eax
	movq	-8(%rbp), %rbx
	leave
	ret

一些结论:

  • 虚表指针是在对象的构造函数中赋值的;
  • 继承关系中,虚表指针会被多次赋值;
  • 虚函数的调用增加了利用虚表指针取得虚函数地址的过程;

标签:draw,函数,16,rdx,rbp,rax,movq
From: https://www.cnblogs.com/amazzzzzing/p/17545135.html

相关文章

  • 15:vue3 组件生命周期函数应用
    1<template>2<h3>组件生命周期函数应用</h3>3<!--验证Dom结构加载时机-->4<pref="name">我的内容</p>5<!--模拟网络加载数据-->6<ul>7<liv-for="(item,index)inbanner":key="item.id&q......
  • 82.函数指针?
    82.函数指针?  函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。例如://比较两个string对象的长度boollengthCompare(conststring&,conststring&);  该函数的类型是bool(conststri......
  • 纯虚函数和抽象类
    想要在基类中定义虚函数实现多态,但是有不希望这个基类可以实例化,可以将虚函数定义为纯虚函数。 格式如下:virtual返回值类型函数名()=0;例:virtualintfun()=0; 纯虚函数的特性:1.包含纯虚函数的类称为抽象类。之所以说它抽象,是因为它无法实例化,也就是无法创建对象......
  • 如何实现Python 函数的参数的具体操作步骤
    Python函数的参数在Python中,函数是一种可重复使用的代码块,用于执行特定的任务。函数可以接受参数,参数是函数中用于接受输入值的变量。Python中的函数参数非常灵活,可以有多种形式。本文将介绍Python函数参数的不同类型以及它们的使用方法。位置参数位置参数是指根据参数的......
  • 高等数学——函数的连续性和间断点
    函数的连续性增量:设变量\(u\)从他的一个初值\(u_{1}\)变到终值\(u_{2}\),终值与初值的差\(u_{2}-u_{1}\)就叫做变量\(u\)的增量。\[\Deltau=u_{2}-u_{1}\]增量可正可负。函数\(f(x)\)随\(x\)的变化:\[\Deltay=f(x_{0}+\Deltax)-f(x_{0})\]增量都是变化以后的......
  • 含有分布式电源的配电网日前优化调度粒子群算法的MATLAB程序,目标函数为网络损耗或电压
    含有分布式电源的配电网日前优化调度粒子群算法的MATLAB程序,目标函数为网络损耗或电压偏差,也可两者结合,代码注释详细,有相关参考文献。YID:2630656792919606......
  • 【JavaScript】js 处理复制函数实现
    consthandleCopy=()=>{if(!keywordList.value.length)returnElMessage.warning('没有数据');consttext=JSON.stringify(keywordList.value);constinput=document.createElement('input');input.setAttribute('readonly......
  • MATLAB 基于 GUI窗函数法设计FIR数字滤波器 语音信号处理等多
    MATLAB基于GUI窗函数法设计FIR数字滤波器语音信号处理等多个ID:5145650201954789......
  • toRef与toRefs函数
    toRef作用:创建一个ref对象,其value值指向另一个对象中的某个属性。语法:constname=toRef(person,'name')应用:要将响应式对象中的某个属性单独提供给外部使用时。扩展:toRefs与toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person) Demo5.vue<te......
  • 自定义hook函数
    什么是hook?——本质是一个函数,把setup函数中使用的CompositionAPI进行了封装。类似于vue2.x中的mixin。自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂。 创建hook3文件夹新建usePoint.js文件(文件名以userxxx命名) app.vue<template><button@cl......