多态机制

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

C++ | 动多态 | 虚函数表的更多相关文章

  1. C++ 虚函数表解析

    转载:陈皓 http://blog.csdn.net/haoel 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实 ...

  2. C++ 多态、虚函数机制以及虚函数表

    1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a  = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...

  3. C++迟后联编和虚函数表

    先看一个题目: class Base { public: virtual void Show(int x) { cout << "In Base class, int x = & ...

  4. C++ 知道虚函数表的存在

    今天翻看陈皓大大的博客,直接找关于C++的东东,看到了虚函数表的内容,找一些能看得懂的地方记下笔记. 0 引子 类中存在虚函数,就会存在虚函数表,在vs2015的实现中,它存在于类的头部. 假设有如下 ...

  5. C++虚函数和虚函数表

    前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...

  6. C++ Daily 《5》----虚函数表的共享问题

    问题: 包含一个以上虚函数的 class B, 它所定义的 对象是否共用一个虚函数表? 分析: 由于含有虚函数,因此对象内存包含了一个指向虚函数表的指针,但是这个指针指向的是同一个虚函数表吗? 实验如 ...

  7. C++虚函数表

    大家知道虚函数是通过一张虚函数表来实现的.在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,其内容真是反应实际的函数.这样,在有虚函数的类的实例中,这个表分配在了这个实例的内存中 ...

  8. 对C++虚函数、虚函数表的简单理解

    一.虚函数的作用 以一个通用的图形类来了解虚函数的定义,代码如下: #include "stdafx.h" #include <iostream> using name ...

  9. 深入理解C++虚函数表

    虚函数表是C++类中存放虚函数的一张表,理解虚函数表对于理解多态很重要. 本次使用的编译器是VS2013,为了简化操作,不用去操作函数指针,我使用到了VS的CL编译选项来查看类的内存布局. CL使用方 ...

随机推荐

  1. win7重装系统过程关机 电脑开机黑屏 硬盘无法识别 无法使用u盘启动

    问题:win7重装系统中强制重启导致硬盘无法识别,开机后无法选择使用u盘启动盘启动,电脑黑屏,将硬盘拆掉可以使用u盘启动,使用SATA转接口在win7中有反应但无法识别 无法识别原因:重装系统过程中断 ...

  2. Oracle RAC架构图和常用命令

    转至:https://www.cnblogs.com/yaoyangding/p/15596308.html Oracle RAC(实时应用集群)是Oracle数据库完全高可用体系解决方案中的一个关键 ...

  3. linux中jupyter notebook中切换虚拟环境

    python -m ipykernel install --user --name 虚拟环境名称 --display-name "虚拟环境名称" 然后再打开jupyter note ...

  4. XML序列化与反序列化接口对接实战,看这篇就够了

    关键字:c# .NET XML 序列化 反序列化 本文为接口对接实践经验分享,不对具体的XML概念定义进行阐述:涉及工具类及处理方法已在生产环境使用多年,可放心使用.当然如果你发现问题,或有不同想法, ...

  5. vue的拖拽vuedraggable组件使用方法

    <template>   <div id="app">     <vuedraggable class="wrapper"     ...

  6. Linux-系统的延时和定时

    1.系统的延时任务 at 时间 时间后回车 就可以进入编辑了 完成后按ctrl+d提交 at 09:46 #设定任务的执行时间 at> touch /mnt/file{1..9} #任务的动作 ...

  7. PhpStrom 好用的代码小地图插件

    类似SublimeText的Mini Map插件 ,废话不多直接上 安装 打开File -> Settings -> Plugins -> 搜索CodeGlance -> in ...

  8. 程序语言与编程实践2-> 蓝桥杯C/C++备赛记录1 | 入门了解与首周训练

    寒假前班主任帮我们报了名,是得好好准备准备.作为一个CSer,coding能力一定不能太弱.我反思,好久没写C/C++代码了,净是些随手写的python脚本,刚开始上手题目bug一大堆. 由于也不是啥 ...

  9. 新建vue3.0 项目—步骤详细介绍

    一.环境搭建 1.安装node环境(版本≥ 8.9) 2.安装vue-cli3.0 npm install @vue/cli -g 此处省略安装步骤截图了,相信各位都已装好了,下图为查看版本结果: 二 ...

  10. Net中异步

    同步和异步1.同步是指只启动一个线程2.异步是指同时启动多个线程3.同步方法会卡界面,异步方法不会卡界面 原因:异步方法启动了子线程执行任务,主线程得到释放4.同步方法会慢.异步方法会快 原因:异步启 ...