zoukankan      html  css  js  c++  java
  • C++ | 动多态 | 虚函数表

    多态机制

    C++语言有三大特性:封装、继承、多态。

    其中所谓的多态,即 “同一接口,不同形态”。接口在我们 C/C++ 语言中可以理解为函数名,不同形态可以理解为函数执行的功能不同。

    而多态又主要分为静多态和动多态。
    静多态: 在编译阶段确定函数的调用
    动多态: 运行阶段确定函数的调用
    宏多态: 在预编译阶段确定函数的调用

    而我们今天要讨论的就是动多态。

    动多态

    提到动多态就要提到虚函数,其中虚函数的作用是为动多态提供支持。而我们说动多态发生在运行阶段,那么我们就要先了解运行阶段发生了哪些事情。

    从一个 .c/.cpp 文件到最终的可执行程序,主要有以下四个步骤:预编译、编译、汇编和链接。
    在这里插入图片描述
    在链接成为可执行程序之后,就可以运行了,也就是我们所说的运行阶段。

    虚函数表

    下面我们采用推理的方式,一步步探究动多态的实现原理。

    分析1:我们说动多态在运行阶段确认函数的调用 ===》而函数的调用需要拿到函数的入口地址 ===》

    所有的文件都是存储在磁盘上的,而在运行阶段所有的数据都是在内存中,要想实现在运行时调用函数,就要确保内存中保存着这些函数的入口地址。那么问题来了,我们在运行中能不能拿到函数的入口地址呢?或者说,要通过怎样的方式才能拿到函数的入口地址呢?

    要回答这个问题,我们就得先搞清楚链接阶段发生了哪些事情。

    链接阶段主要做了以下四件事情:

    1. 合并段和符号表
    2. 符号解析
    3. 分配地址和空间
    4. 指令段(.text)符号重定位

    函数的入口地址在符号表中存放,在链接完成之后我们就已经拿到了函数的入口地址,但是符号表在磁盘上存放

    分析磁盘文件:
    在windows系统上文件以PE格式存放,在Linux系统上文件以ELF格式存放,下面以Linux的ELF文件为例分析文件格式。

    ELF文件格式大致如下,一个ELF HEADER头部信息、.text指令段、.data数据段、.bss数据段(不存在)、.comment 注释信息段等等。
    在这里插入图片描述
    使用命令 objdump -h obj.o查看目标文件的段布局
    在这里插入图片描述
    运行阶段,程序通过两个加载器把指令段和数据段信息加载到内存中,而在运行阶段我们根本就没有把符号表加载到内存中,也就拿不到函数的入口地址。(ps:函数在汇编阶段完成后,普通函数的入口地址已经写死到指令中了,因此在运行阶段不需要加载符号表。)

    数据段有 .data段、.bss段、 .rodata段,以下为程序加载示意图
    在这里插入图片描述

    分析2:在内存中拿到函数的入口地址 ===》 程序运行时从可执行程序中只加载 指令数据 ===》 把函数的入口地址放在可加载的区域

    :可加载的区域就指令段和数据段,那么把入口地址放在指令段还是数据段呢?
    :嗯,地址应该算是数据吧,就如同指针所指向的数据一样,存放的都是地址。

    那么最终的问题,就变成怎样把函数入口地址放入数据段中。

    :那么函数入口地址是哪个阶段产生的呢?
    :函数、全局变量、静态变量的虚拟地址在编译时就可确定。

    编译阶段会生成函数符号和入口地址,按照普通函数的处理方式,函数符号会写进符号表中,而我们现在让它在编译阶段时在符号表放一份,在数据段再放一份。这样数据段中就有了一份函数入口地址的拷贝。其中数据段指的是 .rodata(只读数据段)。因此,在 .rodata 数据段中就有了一份符号表。

    在这个 .rodata 段中存放的函数数据集合我们用一个结构体vftable表示,而这个函数集合也就是我们讨论的虚函数表

    通过层层推理,终于水落石出了。我们找出了多态实现的理论依据,即虚函数表的生成。

    前面也提到了,虚函数为多态提供了支持,因此对于虚函数的处理应该满足以上的推论,下面让我们正向的推导一遍整个流程:

    虚函数的处理流程:

    virtual 关键字声明函数为虚函数 ===》 编译阶段生成符号把函数符号写入符号表并生成虚函数表 ===》 汇编、链接生成可执行程序 ===》 加载指令和数据 ,运行可执行程序

  • 相关阅读:
    sql over(partition by) 开窗函数的使用
    利用curl函数处理GET数据获取微信公众号的access_token
    2018.4.12
    字段和属性
    C#实现回车键登录
    判断DataTable里面数据是否有重复数据
    一个强大的人民币大写转换的正则表达式
    C#将image中的显示的图片转换成二进制
    遍历Dev LayoutControl中的所有控件信息
    遍历窗体中所有控件的信息
  • 原文地址:https://www.cnblogs.com/TaoR320/p/12680126.html
Copyright © 2011-2022 走看看