一、直接调用、间接调用

  • 面向对象的三大特性:封装、继承、多态

  • 多态是最重要的,如果把虚函数掌握好了,多态就容易理解了

  • 看反汇编的硬编码:E8 ...:直接调用;FF15 ...:间接调用

    注入shellcode那章学过直接调用;IAT表那章学过间接调用

  • 通过对象直接去调用普通成员方法或者虚函数:从反汇编层面讲,没有任何区别。即都是传入this,接着使用E8,直接调用

    #include "stdafx.h"
    class Person{
    public:void method1(){printf("method1\n");}virtual void method2(){   //虚函数printf("method2\n");}
    };
    int main(int argc, char* argv[]){       Person person;person.method1();person.method2();return 0;
    }
    

  • 如果用指针调用普通成员方法,和用对象调用没有区别;如果用指针调用虚函数:发现调用之前先做了一些额外的操作,而使用的是FF12间接调用

    #include "stdafx.h"
    class Person{
    public:void method1(){printf("method1\n");}virtual void method2(){printf("method2\n");}
    };
    int main(int argc, char* argv[]){       Person person;Person* p = &person;  //用指针的方式调用p->method1();p->method2();return 0;
    }
    

二、虚函数表反汇编分析

1.研究大小

  • 不管一个类中有多少虚函数,都只多出来4字节大小

    #include "stdafx.h"
    class Person{int x;int y;
    public:virtual void method1(){printf("method1\n");}virtual void method2(){printf("method2\n");}
    };
    int main(int argc, char* argv[]){       Person person;printf("%d",sizeof(person));  //12return 0;
    }
    

2.虚函数表地址

  • 既然有虚函数就会多出来4字节,我们反汇编分析一下这4字节是什么,在哪里?

    #include "stdafx.h"
    class Person{int x;int y;
    public:Person(){x = 1;y = 2;}virtual void method1(){printf("method1\n");}virtual void method2(){printf("method2\n");}
    };
    int main(int argc, char* argv[]){       Person person;printf("%d",sizeof(person));return 0;
    }
    

    • person对象首地址在0x12FF74,所以发现:这个多出来的4字节所在位置,就是类的对象的首地址,再往后才是x和y。所以对象的首地址中存储的就是虚函数表的地址值

3.调用虚函数的反汇编

  • 我们来分析一下使用指针调用虚函数时编译器做了什么:

    #include "stdafx.h"
    class Person{int x;int y;
    public:Person(){x = 1;y = 2;}virtual void method1(){printf("method1\n");}virtual void method2(){printf("method2\n");}
    };
    int main(int argc, char* argv[]){       Person person;Person* p = &person;p->method1();p->method2();return 0;
    }
    

    • 先把[ebp-0x10]存到ecx,ebp-0x10中的值即为0x12FF74(即对象首地址),所以现在ecx中的值为0x12FF74

    • 接着把[ecx]存到edx,即把0x12FF74中的值存到edx,所以现在edx中年的值为0x42202C(虚函数表地址)

    • 最后call [edx],即call的是0x42202C地址中的值!(间接调用)

      上述反汇编中一定要注意到底取的是地址还是地址中的值!即要注意有没有中括号[]

    • 那我们就看看0x422202C地址中的值到底是什么!发现调用虚函数method1时,call的实际上是0x401064;调用虚函数method2时,call的实际上是0x40105A。(这两个值就是虚函数的真正地址!而所有虚函数的地址集中存放的地方就是虚函数表!

  • 综上:类中有虚函数,多出的4字节属性是一个地址,指向一张虚函数表,里面存储了所有虚函数的地址

  • 为了验证这是不是真的虚函数地址,可以使用函数指针的方法验证:定义一个函数指针,将这个虚函数地址值赋给函数指针,接着调用一下,看是否真的完成了method1该干的事情即可验证

    #include "stdafx.h"
    class Person{int x;int y;
    public:Person(){x = 1;y = 2;}virtual void method1(){printf("method1\n");}virtual void method2(){printf("method2\n");}
    };
    int main(int argc, char* argv[]){       Person person;typedef void (*pMethod)(void);   //函数指针声明pMethod pm1 = (pMethod)(*(int*)(*(int*)&person));  //定义pm1函数指针并将虚函数表中第一个地址值赋给pm1pMethod pm2 = (pMethod)(*((int*)(*(int*)&person) + 1));  //定义pm2函数指针并将虚函数表中第二个地址值赋给pm2//接着用函数指针的方式调用pm1();  //method1pm2();  //method2return 0;
    }
    

    发现:这两个地址,真的是虚函数的地址!

  • 综上,有虚函数的类的对象的内存空间是这样的:

三、作业

1.重载和重写

  • 重载:一个类中,函数名一样,参数的个数或类型不同
  • 重写:子类中的函数和其父类中的函数名字、参数、返回值一模一样(函数覆盖)

2.继承与虚函数表

  • 单继承无函数覆盖(打印Sub对象的虚函数表):

    #include "stdafx.h"
    class Base{
    public:         virtual void Function_1(){          printf("Base:Function_1...\n");           }           virtual void Function_2(){          printf("Base:Function_2...\n");           }           virtual void Function_3(){          printf("Base:Function_3...\n");           }
    };
    class Sub:public Base{
    public:         virtual void Function_4(){          printf("Sub:Function_4...\n");            }           virtual void Function_5(){          printf("Sub:Function_5...\n");            }           virtual void Function_6(){          printf("Sub:Function_6...\n");            }
    };
    int main(int argc, char* argv[]){       Sub sub;Sub* subp = ⊂subp->Function_1();subp->Function_4();return 0;
    }
    

    sub对象的虚函数表如下:

  • 单继承有函数覆盖(打印Sub对象的虚函数表)

    #include "stdafx.h"
    class Base{
    public:         virtual void Function_1(){          printf("Base:Function_1...\n");           }           virtual void Function_2(){          printf("Base:Function_2...\n");           }           virtual void Function_3(){          printf("Base:Function_3...\n");           }
    };
    class Sub:public Base{
    public:         virtual void Function_1(){          printf("Sub:Function_1...\n");            }           virtual void Function_2(){          printf("Sub:Function_2...\n");            }           virtual void Function_6(){          printf("Sub:Function_6...\n");            }
    };
    int main(int argc, char* argv[]){       Sub sub;Sub* subp = ⊂subp->Function_1();  //重写,则最终调用子类中的Function1()subp->Function_2();  //重写subp->Function_3();subp->Function_6();return 0;
    }
    

    sub对象的虚函数表如下:(只有4个了)

滴水三期:day44.2-虚函数表相关推荐

  1. C++对象的内存布局1---基础篇----C++ 虚函数表解析

    [-] 前言 虚函数表 一般继承(无虚函数覆盖) 一般继承(有虚函数覆盖) 多重继承(无虚函数覆盖) 多重继承(有虚函数覆盖) 安全性 结束语 附录一:VC中查看虚函数表 附录 二:例程 前言 C++ ...

  2. C++中的虚函数表介绍

            在C++语言中,当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定.因为我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有虚函数都必须有定义.通常情况下,如果我们不使 ...

  3. 虚函数表剖析,网上转的,呵呵

    http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html C++虚函数表解析(转) C++中的虚函数的作用主要是实现了多态的机制.关于多 ...

  4. C++ 虚函数表解析

    转载自 https://blog.csdn.net/zhou191954/article/details/44919479 C++ 虚函数表解析 前言 C++中的虚函数的作用主要是实现了多态的机制.关 ...

  5. C++对象内存布局--①测试虚函数表属于类

    C++对象内存布局--①测试虚函数表属于类 测试1:同一个类的多个对象共享同一张虚函数表.   //虚函数表.cpp //2010年8月18日 //测试虚函数表,说明虚函数表属于类所有.同一个类的多个 ...

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

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

  7. 虚函数表 vtable

    如果一个类包含了虚函数,那么在创建对象时会额外增加一张表,表中的每一项都是虚函数的入口地址.这张表就是虚函数表,也称为 vtable. 可以认为虚函数表是一个数组. 为了把对象和虚函数表关联起来,编译 ...

  8. 图解C++虚函数 虚函数表

    图解C++虚函数 2016年07月02日 17:47:17 海枫 阅读数:5181 标签: 虚函数c++g++对象模型C++虚函数更多 个人分类: C/C++/linux 版权声明:本文为博主原创文章 ...

  9. C/C++杂记:虚函数的实现的基本原理 虚函数表

    Malecrab 博客园 首页 新随笔 联系 订阅 管理 C/C++杂记:虚函数的实现的基本原理 1. 概述 简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函 ...

  10. C++虚函数表,虚表指针,内存分布

    虚函数表和内存分布那一块转载自:https://blog.twofei.com/496/ 虚函数效率转载自:https://www.cnblogs.com/rollenholt/articles/20 ...

最新文章

  1. 牛客寒假6-B.煤气灶
  2. sxssfworkbook设置单元格格式_用好条件格式,实现数据可视化,尤其是第8个技巧!...
  3. inner join 和 exists 效率_19条效率至少提高3倍的MySQL技巧
  4. silverlight之ToolTipService
  5. windows server 2008解决无法PING通问题
  6. java 拼sql最大长度,java.sql.SQLNonTransientConnectionException: 用户 ID 长度 (0) 超出 1 到 255 的范围...
  7. 计算机科学技术专业词汇,计算机专业一些单词
  8. 第9天Sqltie数据库
  9. Kicad改主题 层颜色 (护眼黑底 层颜色类似立创EDA 或者Altium Designer)
  10. 3D游戏-作业三-空间与运动
  11. picpick截图处理后关闭未保存的图像时不再询问
  12. 分享两年折腾DIY-NAS的经验和小技巧
  13. Dhrystone简介
  14. facenet 人脸识别库的搭建和使用方法(二)
  15. 你好你好你好你好你好你好
  16. 使用 IndexedDB 进行大数据存储
  17. Day09 - 面向对象进阶
  18. 如何设计大型集团一体化IT运维系统
  19. 怎么让自己平静下来!
  20. 谷智网Ebay大账户

热门文章

  1. c语言编程单片机中的sbit,用sbit定义可位寻址的特殊功能寄存器时的地址转换-51单片机C编程...
  2. 【unity 保卫星城】--- 开发笔记08(太空站篇)
  3. 搞个气氛 用MATLAB画一棵精致的圣诞树
  4. 1+x证书Web前端开发HTML+CSS专项练习测试题(八)
  5. 爬取电影《无双》影评数据,分析,可视化
  6. CCF-201809-3
  7. 【破事氵】在Linux环境中让程序在后台运行
  8. docker搭建searx_Searx – 尊重隐私的开源搜索引擎
  9. 小程序模板平台怎么选?
  10. C语言--正弦、余弦函数