汇编视角分析C++虚函数实现原理

2023-10-27

1.概述

        虚函数是c++语言非常重要的机制,日常的c++编程工作中经常使用虚函数,通过汇编视角来探究虚函数的实现原理,有助于深刻理解虚函数的内部机制。尤其要说明的是:c++语法规范并没有规定虚函数的具体实现方案,不同的编译器实现方式可以不同,本文基于arm32平台,g++编译器来分析虚函数表的实现机制,基本主流编译器的原理基本是相似的,所以理解g++实现方案再去分析其他编译器的实现也是类似的。

2.示例代码

class State {
public:
    State(int id) { 
        this->mId = id;
    }
    
    State(const State &state) {
        printf("State copy constructor\n");
    }
    
    virtual void dump() {
        printf("State mId:%d\n", mId);
    }
                                                                                                                                                           
public:
    int mId;
};

class AudioState : public State {
public:
    AudioState(int id) : State(id) {
    }

    void dump() {
        printf("AudioState mId:%d\n", mId); 
    }
};
int main(){
    printf("%d\n" ,sizeof(AudioState));
    State *ps = new AudioState(1);
    ps->dump();
    return 0;
}

代码编译和反汇编:

arm-linux-androideabi-g++ -pie -fPIE -o vtable vtable.cpp

arm-linux-androideabi-objdump vtable > vtable.txt

3. 虚函数调用实现基本原理

1. 如果是通过类指针调用相应的虚函数,通过vtable实现调用。

2. 如果子类重新实现了虚函数,vtable中对应项填写子类的函数(如上图),否则,填写父类德函数。

如下代码:

AudioState *ps = new AudioState(1);
ps->dump();

调用dump函数的时候,首先从AudioState对象的vptr指针拿到虚函数表(vtable),然后获取到虚函数表中dump函数对应的表项,所以最终调用到AudioState::dump函数。下面通过g++ arm汇编分析上述调用过程,帮助我们更深刻的理解虚函数实现原理。

4.虚函数调用实现

1. AudioState虚函数表在哪里构建?

答:AudioState构造函数里面

下面分析AudioState的汇编代码:

000014b0 <_ZN10AudioStateC1Ei>:
    14b0:   e92d4810    push    {r4, fp, lr}
    14b4:   e28db008    add fp, sp, #8
    14b8:   e24dd00c    sub sp, sp, #12

    //fp-16栈处存储对象指针,即对象首地址
    14bc:   e50b0010    str r0, [fp, #-16]

    //fp-20栈处存储构造函数的参数,即id参数
    14c0:   e50b1014    str r1, [fp, #-20]  ; 0xffffffec

    //以下两行将r4 = 10abc(1500地址处的值) + 14d0 = 11F8c
    14c4:   e59f4034    ldr r4, [pc, #52]   ; 1500 <_ZN10AudioStateC1Ei+0x50>
    14c8:   e08f4004    add r4, pc, r4

    14cc:   e51b3010    ldr r3, [fp, #-16]
    14d0:   e1a00003    mov r0, r3
    14d4:   e51b1014    ldr r1, [fp, #-20]  ; 0xffffffec
    14d8:   ebffffd0    bl  1420 <_ZN5StateC1Ei>

    //r3存储AudioState对象的首地址
    14dc:   e51b3010    ldr r3, [fp, #-16]

    //r2从如下地址取值:ffffffa4(1504处的值, -92的补码) + r4= 11F8c - (5c) = 11F30
    //r2 = [11F30] = 11c68, 11c68就是AudioState虚函数表
    14e0:   e59f201c    ldr r2, [pc, #28]   ; 1504 <_ZN10AudioStateC1Ei+0x54>
    14e4:   e7942002    ldr r2, [r4, r2]

    //r2 取AudioState虚函数表的第三项(前面存储了typeinfo相关项), r2 = 11c70
    14e8:   e2822008    add r2, r2, #8

    //将r2 11c70这个地址存储对象的首地址,即vptr = 11c70
    14ec:   e5832000    str r2, [r3]
    14f0:   e51b3010    ldr r3, [fp, #-16]
    14f4:   e1a00003    mov r0, r3
    14f8:   e24bd008    sub sp, fp, #8
    14fc:   e8bd8810    pop {r4, fp, pc}
    1500:   00010abc            ; <UNDEFINED> instruction: 0x00010abc
    1504:   ffffffa4            ; <UNDEFINED> instruction: 0xffffffa4

00011c68 <_ZTV10AudioState>:                                                                                                                               
   11c68:   00000000    andeq   r0, r0, r0
   11c6c:   00011c84    andeq   r1, r1, r4, lsl #25
   11c70:   00001508    andeq   r1, r0, r8, lsl #10    //1508即AudioState::dump
   11c74:   00000000    andeq   r0, r0, r0

00001508 <_ZN10AudioState4dumpEv>:
    1508:   e92d4800    push    {fp, lr}
    150c:   e28db004    add fp, sp, #4
    1510:   e24dd008    sub sp, sp, #8
    1514:   e50b0008    str r0, [fp, #-8]
    1518:   e51b3008    ldr r3, [fp, #-8]
    151c:   e5933004    ldr r3, [r3, #4]
    1520:   e59f2014    ldr r2, [pc, #20]   ; 153c <_ZN10AudioState4dumpEv+0x34>
    1524:   e08f2002    add r2, pc, r2
    1528:   e1a00002    mov r0, r2
    152c:   e1a01003    mov r1, r3
    1530:   ebffff14    bl  1188 <printf@plt>
    1534:   e24bd004    sub sp, fp, #4
    1538:   e8bd8800    pop {fp, pc}
    153c:   0000d434    andeq   sp, r0, r4, lsr r4

根据上面汇编代码14ec行,最终将r2(11c70)存入r3中指向地址,其中r2时虚函数表的地址,对应的表项内容:1508,对应的是AudioState::dump函数地址,说明g++编译器会把vptr放在对象的开端处。

2. 虚函数调用流程?

答:通过vptr找到对应的虚函数

main函数的汇编代码:

000013d4 <main>:
    13d4:   e92d4810    push    {r4, fp, lr}
    13d8:   e28db008    add fp, sp, #8
    13dc:   e24dd00c    sub sp, sp, #12
    13e0:   e3a00008    mov r0, #8
    13e4:   fa000094    blx 163c <_Znwj>
    13e8:   e1a04000    mov r4, r0
    13ec:   e1a00004    mov r0, r4
    13f0:   e3a01001    mov r1, #1
    13f4:   eb00002d    bl  14b0 <_ZN10AudioStateC1Ei>

    //将AudioState对象指针ps存储fp-16处
    13f8:   e50b4010    str r4, [fp, #-16]

    //ps指针读入r3
    13fc:   e51b3010    ldr r3, [fp, #-16]

    //vptr读入r3
    1400:   e5933000    ldr r3, [r3]

    //AudioState::dump虚函数地址读入r3,即1508
    1404:   e5933000    ldr r3, [r3]
    1408:   e51b0010    ldr r0, [fp, #-16]

    //跳转到dump函数执行
    140c:   e12fff33    blx r3
    1410:   e3a03000    mov r3, #0
    1414:   e1a00003    mov r0, r3
    1418:   e24bd008    sub sp, fp, #8
    141c:   e8bd8810    pop {r4, fp, pc}

注意:如果直接使用类对象调用虚函数,不会通过虚函数,汇编代码中可以看到回直接bl跳转相应对象的函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

汇编视角分析C++虚函数实现原理 的相关文章

随机推荐

  • 27.EI文章复现《高比例清洁能源接入下计及需求响应的配电网重构》

    下载地址 高比例清洁能源接入下计及需求响应的配电网重构 1主要内容 该程序复现 高比例清洁能源接入下计及需求响应的配电网重构 以考虑网损成本 弃风弃光成本和开关操作惩罚成本的综合成本最小为目标 针对配电网重构模型的非凸性 引入中间变量并对其
  • Templates and Classes___CH_19

    19 1 Template classes Templates and container classes Template classes in the standard library Now that we ve covered te
  • 分库分表

    名词解释 库 database 表 table 分库分表 sharding 为什么要分库分表 移动互联网时代 海量的用户每天产生海量的数量 比如 用户表 订单表 交易流水表 以支付宝用户为例 8亿 微信用户更是10亿 订单表更夸张 比如美团
  • Python 20.opencv 直方图均衡化,CLAHE(对比度改变)

    import cv2 import numpy as np img cv2 imread pic1 png 0 进行直方图均衡化 equ cv2 equalizeHist img CLAHE有限对比适应性直方图均衡化 作用 限制对比度下降
  • 第一个项目单个交换机接入网络

    按图片上的要求来将一个交换机接入网络 配置明细路由 vlanif当网关
  • MATLAB中LSTM时序分类的用法与实战

    MATLAB中LSTM时序分类的用法与实战 说明 本教程适用于R2018b版本的matlab 不知道R2018a有没有 但是2017版本的肯定是没有LSTM工具箱的了 所以版本低的趁这个机会卸载然后重新下载安装吧 引用参考 1 matlab
  • 基于昇腾CANN的推理应用开发--语义分割(Python)

    前情提要 基于 Python 开发 通过网络模型加载 推理 结果输出的部署全流程展示 快速熟悉并掌握语义分割基本开发流程 目录 1 内容及目标 1 1 内容 1 2 目标 1 3 先导知识 2 理解原始模型 2 1 网络结构 2 2 查看模
  • stable diffusion model训练遇到的问题【No module named ‘triton‘】

    一天早晨过来 发现昨天还能跑的diffusion代码 突然出现了 No module named triton 的问题 导致本就不富裕的显存和优化速度雪上加霜 因此好好探究了解决方案 首先是原因 由于早晨过来发现 电脑重启 导致了 训练终止
  • 冒泡排序与选择排序区别

    冒泡排序 冒泡排序 BubbleSort 的基本概念是 依次比较相邻的两个数 将小数放在前面 大数放在后面 即在第一趟 首先比较第1个和第2个数 将小数放前 大数 放后 然后比较第2个数和第3个数 将小数放前 大数放后 如此继续 直至比较最
  • LeetCode 热门100题 2.两数相加

    public class Solution public ListNode addTwoNumbers ListNode l1 ListNode l2 int num1 ListToArray l1 int num2 ListToArray
  • 开源创作工具一览

    GIMP 2 8 http www gimp org 常见的位图编辑工具 不再赘述 新的 2 8 版本增加了单窗口模式 层分组等功能 Fedora 17 安装 pkcon install gimp MyPaint http mypaint
  • 简单的数据库关系表建立

    表与表之间一般存在三种关系 即一对一 一对多 多对多关系 下面分别就三种关系讲解数据库相关设计的思路和思考过程 1 一对一关系 例如 下面的一张表 保存了人的相关信息 有男有女 要求查处所有的夫妻 sql代码 CREATE TABLE IF
  • android上架备案公钥和md5获取工具

    最近很多公司上架遇到了一个问题 就是要提供app的备案证明 现在android上架都需要备案了 但是我们的证书都是通过工具生成的 哪里知道公钥和md5那些东西呢 无论安卓备案还是ios备案都需要提供公钥和md5 包括ios的备案也是 找了很
  • uni-app使用swiper实现tab左右滑动下拉无法触发onReachBottom页面生命周期

    注意要点 1 通过uni getSystemInfo获取设备具体高度 定义swiper的高度 将swiper撑开 2 利用scroll view视图容器的 scrolltolower属性实现触底加载 3 点击tab切换也会触发swiper的
  • MQTT 5.0 Reason Code 介绍与使用速查表

    Reason Code Reason Code 在 MQTT 中的主要作用是为客户端和服务端提供更详细的反馈 比如我们可以在 CONNACK 报文中将用户名或密码错误对应的 Reason Code 反馈给客户端 这样客户端就能够知道自己无法
  • 使用matplotlib和seaborn绘图的常用参数

    plt grid b True ls color 606060 设置网格线 b控制网格线 ls设置网格线的样式 plt tick params labelsize 20 轴数值大小 labelsize设置数值展示大小 plt ylabel
  • JeeSite 是什么、概述

    见JeeSite官网 http jeesite4 mydoc io 前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到教程 总体概述 快速访问 JeeSite 官网地址 http jeesite c
  • 手把手教你归并排序(递归)

    今天 小编继续带大家学习排序算法 这次我们一起来学习归并排序的递归算法 多多点赞支持博主 速更非递归算法哦 目录 一 实现原理 二 代码实现 三 注意事项与缺点 一 实现原理 归并算法的实现与快排类似 都是采用了分治递归的思路 它的时间复杂
  • 关于vue中父页面的生命周期和子组件生命周期关系导致的报错问题解决

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 问题出现的原因 二 问题解决的方法 1 使用v if删除和添加子组件标签 2 合理设置子组件生命周期的函数 三 父页面和子组件的生命流程描述 总结 前言
  • 汇编视角分析C++虚函数实现原理

    1 概述 虚函数是c 语言非常重要的机制 日常的c 编程工作中经常使用虚函数 通过汇编视角来探究虚函数的实现原理 有助于深刻理解虚函数的内部机制 尤其要说明的是 c 语法规范并没有规定虚函数的具体实现方案 不同的编译器实现方式可以不同 本文