1.什么是虚函数?
2.虚函数有什么用?
3.怎样才产生虚函数表?
4.虚函数表存放在什么位置?
5.虚函数表长什么样?
6.为什么用虚函数实现多态时需要用到指针或者引用,而不能直接赋值?

假设有这么一个需求

class Creature
{public:int hp;int mp;char* name;void Attack();void Defend();void Dead();Creature(char* name);
};class Player:public Creature
{public:int nSmart;int nPower;void Buy();void Dead();Player(char* name);
};

父类和子类具有同名函数,void Dead();

void Creature::Dead()
{printf("I am Dead.\n");
}
void Player::Dead()
{printf("game over.\n");
}

我们创建了一个函数去调用父类和子类的void Dead();我们想要这么一个结果,当传入父类对象时,调用父类的同名函数,传入子类对象时调用子类的同名函数。(所谓多态)

void DoSometing(Creature* c)
{c->Dead();
}

至于为什么形参里面是Creature* c而不是Player* p,是由于Player权限(//过大)的指针访问Creature结构体是不安全的,而Creature权限的指针访问Player结构体是安全的(所谓继承的特性)

那为什么不能这样?(问题6),这个问题稍后解决

void DoSometing(Creature c)
{c.Dead();
}

调用DoSometing时的结果

I am Dead.
I am Dead.

由于Creature原则上是指向一个Creature类型的结构体,调用Creature相关的Dead就合情合理,但我们的要求是,让Creature指向Player的结构体时,调用Player相关的Dead就很无理取闹
但由于C++的特性,在父类的同名函数前加上virtual,在子类的同名函数前加上override(不加也行,不过加了的情况下,当要重写的函数名不小心写错了,编译器会报错,起到提醒的作用),那什么是虚函数呢?(问题1)加上virtual关键字的函数就是虚函数,虚函数的作用就是实现我们刚才的那个需求(问题2)

virtual void Dead();
void Dead() override;

现在DoSometing的执行结果就达到预期了

I am Dead.
game over.

那么现在问题是加上关键字后,C++帮我们做了哪些额外的工作来实现这一个需求,那直接看汇编代码

lea ecx,dword ptr ss:[ebp-18] //局部变量Player

Player本身的成员变量只占20字节,但现在居然占了24字节

00EFFB50  00F15B40  @[ñ.  x86test.const Player::`vftable'
00EFFB54  00000064  d...
00EFFB58  00000032  2...
00EFFB5C  00F15BD0  Ð[ñ.  "sanqiu"
00EFFB60  0000000A  ....
00EFFB64  00000007  ....

Player结构体的第一个成员指向了vftable(虚函数表),所以第一成员又叫做虚函数表指针,所以说,虚函数表的位置可以从结构体第一个成员中找到(问题4),当然,是在结构体存在虚函数表的前提下。后面五个成员跟以前一样

 lea eax,dword ptr ss:[ebp-28]

Creature本身的成员变量只占12字节,但现在居然占了16字节

00EFFB40  00F15B34  4[ñ.  x86test.const Creature::`vftable'
00EFFB44  00000064  d...
00EFFB48  00000032  2...
00EFFB4C  00F15BD0  Ð[ñ.  "sanqiu"

Creature结构体的第一个成员也指向了vftable(虚函数表),后面的三个成员跟以前一样。
所以现在我们明白了,当父类函数有virtual关键字的函数时就会有虚函数表,当子类函数重写父类基函数时也会有虚函数表,那么又产生了一个问题,子类不重写父类的虚函数会怎么样?,答案是,仍然会产生一个虚函数表(问题3)
可以推理得到,即使无论子类是否重写父类的虚函数,都会存在一个虚函数表,但是,这两个虚函数表的内容肯定是不一样(这是肯定的,不然控制台也不会打印出不同的结果了)
那虚函数长什么样呢?(问题5),可以把虚函数表理解为一个以0x00000000结尾的特殊指针字符串(一般的字符串以0x0或0x00结尾),就像这样:函数地址,函数地址,函数地址,0x00000000。当然,在X64程序中就是0x0000000000000000了
当不重写时,Player的虚函数表长这样
函数地址(I am dead的那个函数)
0x00000000
当重写时,Player的虚函数表长这样
函数地址(game over的那个函数)
0x00000000
所以说所谓重写父类虚函数,就是重写从父类继承过来的虚函数表中的对应函数地址!!!
那最后一个问题就是问题6了,解决这个问题需要分析DoSomething里面的代码

void DoSometing(Creature* c)
{c->Dead();
}
关键汇编代码
mov eax,dword ptr ss:[ebp+8]   //eax = 对象指针
mov edx,dword ptr ds:[eax]      //edx = 虚函数表指针
mov ecx,dword ptr ss:[ebp+8]   //ecx = 对象指针
mov eax,dword ptr ds:[edx]      //eax = 虚函数(I am dead 或者 game over)
call eax                        //调用 //在汇编下,虚函数的功能一目了然
void DoSometing(Creature c)
{c.Dead();
}
关键汇编代码
lea ecx,dword ptr ss:[ebp+8]
call x86test.B9115E             //C++根本没使用虚函数表,而是直接CALL了 I am dead的那个//函数(x86test.B9115E)

现在,问题6也解决完毕了
但是在汇编层面,C++完全有能力做到在传对象的情况下实现多态,因为Creature c 和 Creature *c传递参数的方式完全相同, 但C++最终却不这么做,那就不得而知了

ps:同一个类的对象的虚函数表指针是一样的,也就是都指向同一张虚函数表,这样比较节省内存

虚函数表和虚函数表指针的汇编分析相关推荐

  1. C++虚函数表、虚函数指针(侯捷)

    C++虚函数表.虚函数指针(侯捷)

  2. C++中虚继承产生的虚基类指针和虚基类表,虚函数产生的虚函数指针和虚函数表

    本博客主要通过查看类的内容的变化,深入探讨有关虚指针和虚表的问题. 一.虚继承产生的虚基类表指针和虚基类表 如下代码:写一个棱形继承,父类Base,子类Son1和Son2虚继承Base,又来一个类Gr ...

  3. 虚函数原理与虚函数表

    目录 一. 虚函数 二.虚函数原理与虚函数表 一. 虚函数 虚函数: 使用 virtual 关键字声明的函数,是动态多态实现的基础. 非类的成员函数不能定义为虚函数. 类的静态成员函数不能定义为虚函数 ...

  4. 虚函数、虚函数表、虚继承

    1.虚函数 虚函数的定义: 虚函数必须是类的 非静态成员函数(且非构造函数),其访问权限是public(可以定义为privateor proteceted, 但是对于多态来说,没有意义),在基类的类定 ...

  5. 虚函数的实质——虚函数表

    目录 虚函数的实质--虚函数表 虚函数表是什么? 虚函数表长什么样? 证明了确实存在隐藏的虚函数表指针 派生类指针转换为基类类型的实质 当基类指针指向派生类对象时,虚函数表会如何变化? 虚函数的实质- ...

  6. 虚函数和纯虚函数及虚函数表

    虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入 ...

  7. 虚函数与纯虚函数以及虚函数表之间的关系

    1.虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.C++中虚函数的作用主要是实现多态机制.所谓多态就是用父类指针指向子类对象,然后通过父类指针调用实际子类的成员函数,这种技术 ...

  8. C++类的虚函数表和虚函数在内存中的位置

    C++类的虚函数表和虚函数在内存中的位置 C++类的虚函数表和虚函数在内存中的位置 虚函数表和虚函数在内存中的位置说明 参考 C++类的虚函数表和虚函数在内存中的位置 虚函数表指针是虚函数表所在位置的 ...

  9. C++虚函数,虚函数表,虚继承,虚继承表

    一.虚函数 类中用virtual关键字修饰的函数. 作用:主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的 ...

  10. 虚函数表 以及 虚函数表的继承过程

    目录 一.虚函数表 和 虚表继承 1.虚函数表 2.虚表继承 (1) 子类未重写父类虚函数 (2) 子类重写了父类虚函数 二.虚表的特点 1.同一个类的对象的虚表指针相同 2.多继承时子类中的两个父类 ...

最新文章

  1. 让你的Tex代码更加美观就这么简单----Tex代码的自动格式化
  2. Abiword页面布局
  3. Spring+Hessian搭建远程方法调用
  4. LeetCoed 5383. 给 N x 3 网格图涂色的方案数
  5. 全球AI人才只有2万多,仅3000人在求职 | 报告
  6. Microsoft Visual Studio 2005 怎么更改安装路径?
  7. Scikit-learn:主要模块和基本使用方法
  8. 二叉树 -- 5.1.1 Binary Tree Level Order Traversal-1 -- 图解
  9. 【数码管识别】需要注意的问题
  10. linux下设置好环境变量要重启计算机
  11. 仿王者荣耀HTML示例代码
  12. hit网络安全实验报告
  13. 如何刷新DNS缓存(Windows,Mac,Chrome)
  14. 宝塔面板nginx跨域配置(跳坑)
  15. while循环的基本用法
  16. 不等式计算机在线使用,不等式传递性在线计算器
  17. JavaWeb项目打包上线简单流程
  18. 专访Riverbed CEO:私有化和出售业务瘦身后的Riverbed更专注
  19. Nim 博弈游戏详解
  20. Unity Google VR Cardboard 后台挂起时陀螺仪仍然占用问题解决

热门文章

  1. 自动驾驶技术(3)- 高精度地图解决自动驾驶的功能痛点
  2. 箫演奏技巧符号大全图解
  3. Revit二次开发——布管系统设置
  4. 毕业季怎么做答辩PPT?
  5. SIRS传染病模型求解及MATLAB实现
  6. python矩阵乘法分治算法_详解矩阵乘法中的Strassen算法
  7. C# Hprose轻量级、跨语言、跨平台的面向对象的高性能远程动态通讯中间件
  8. android平板生产力工具,重塑应用生态,让安卓平板成为生产力工具:华为MatePad Pro简体验!...
  9. 基于STM32制作万能遥控器---2
  10. Java生成简单的验证码图片