我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++虚函数表的原理是什么与怎么使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++虚函数表的原理是什么与怎么使用

这篇文章主要介绍了C++虚函数表的原理是什么与怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++虚函数表的原理是什么与怎么使用文章都会有所收获,下面我们一起来看看吧。

1.虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

假设我们有这样的一个类:

class Base {     public:            virtual void f() { cout << "Base::f" << endl; }            virtual void g() { cout << "Base::g" << endl; }            virtual void h() { cout << "Base::h" << endl; }};

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

typedef void(*Fun)(void);Base b;Fun pFun = NULL;cout << "虚函数表地址:" << (int*)(&b) << endl;cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;// Invoke the first virtual functionpFun = (Fun)*((int*)*(int*)(&b));pFun();

实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

虚函数表地址:0012FED4
虚函数表 &mdash; 第一个函数地址:0044F148
Base::f

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

(Fun)*((int*)*(int*)(&b)+0);  // Base::f()(Fun)*((int*)*(int*)(&b)+1);  // Base::g()(Fun)*((int*)*(int*)(&b)+2);  // Base::h()

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

C++虚函数表的原理是什么与怎么使用

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

2.一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

C++虚函数表的原理是什么与怎么使用

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

对于实例:Derive d; 的虚函数表如下:

C++虚函数表的原理是什么与怎么使用

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

3.一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

C++虚函数表的原理是什么与怎么使用

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

C++虚函数表的原理是什么与怎么使用

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

4.多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

C++虚函数表的原理是什么与怎么使用

对于子类实例中的虚函数表,是下面这个样子:

C++虚函数表的原理是什么与怎么使用

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

5.多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

C++虚函数表的原理是什么与怎么使用

下图中,我们在子类中覆盖了父类的f()函数。

C++虚函数表的原理是什么与怎么使用

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;Base1 *b1 = &d;Base2 *b2 = &d;Base3 *b3 = &d;b1->f(); //Derive::f()b2->f(); //Derive::f()b3->f(); //Derive::f()b1->g(); //Base1::g()b2->g(); //Base2::g()b3->g(); //Base3::g()

6.安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

6.1 通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();b1->f1();  //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

6.2 访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

如:

class Base {    private:            virtual void f() { cout << "Base::f" << endl; }};class Derive : public Base{};typedef void(*Fun)(void);void main() {    Derive d;    Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);    pFun();}

7.结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

7.1 VC中查看虚函数表

我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

7.2 例程

下面是一个关于多重继承的虚函数表访问的例程:

#include <iostream>using namespace std;class Base1 {public:            virtual void f() { cout << "Base1::f" << endl; }            virtual void g() { cout << "Base1::g" << endl; }            virtual void h() { cout << "Base1::h" << endl; }};class Base2 {public:            virtual void f() { cout << "Base2::f" << endl; }            virtual void g() { cout << "Base2::g" << endl; }            virtual void h() { cout << "Base2::h" << endl; }};class Base3 {public:            virtual void f() { cout << "Base3::f" << endl; }            virtual void g() { cout << "Base3::g" << endl; }            virtual void h() { cout << "Base3::h" << endl; }};class Derive : public Base1, public Base2, public Base3 {public:            virtual void f() { cout << "Derive::f" << endl; }            virtual void g1() { cout << "Derive::g1" << endl; }};typedef void(*Fun)(void);int main(){            Fun pFun = NULL;            Derive d;            int** pVtab = (int**)&d;            //Base1's vtable            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);            pFun = (Fun)pVtab[0][0];            pFun();            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);            pFun = (Fun)pVtab[0][1];            pFun();            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);            pFun = (Fun)pVtab[0][2];            pFun();            //Derive's vtable            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);            pFun = (Fun)pVtab[0][3];            pFun();            //The tail of the vtable            pFun = (Fun)pVtab[0][4];            cout<<pFun<<endl;            //Base2's vtable            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);            pFun = (Fun)pVtab[1][0];            pFun();            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);            pFun = (Fun)pVtab[1][1];            pFun();            pFun = (Fun)pVtab[1][2];            pFun();            //The tail of the vtable            pFun = (Fun)pVtab[1][3];            cout<<pFun<<endl;            //Base3's vtable            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);            pFun = (Fun)pVtab[2][0];            pFun();            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);            pFun = (Fun)pVtab[2][1];            pFun();            pFun = (Fun)pVtab[2][2];            pFun();            //The tail of the vtable            pFun = (Fun)pVtab[2][3];            cout<<pFun<<endl;            return 0;}

注:本文年代久远,所有的示例都是在32位机上跑的。

关于“C++虚函数表的原理是什么与怎么使用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“C++虚函数表的原理是什么与怎么使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++虚函数表的原理是什么与怎么使用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++虚函数表的原理是什么与怎么使用

这篇文章主要介绍了C++虚函数表的原理是什么与怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++虚函数表的原理是什么与怎么使用文章都会有所收获,下面我们一起来看看吧。1.虚函数表对C++ 了解的人都应
2023-06-30

c++虚函数的实现原理是什么

C++虚函数的实现原理是通过虚函数表(vtable)和虚函数指针(vptr)来实现的。当一个类中声明了虚函数时,编译器会在该类的对象中添加一个指向虚函数表的虚函数指针(vptr)。虚函数表是一个存储类的虚函数地址的表格,每个类对象都有一个
c++虚函数的实现原理是什么
2024-02-29

C++的多态与虚函数是什么

这篇文章主要介绍“C++的多态与虚函数是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“C++的多态与虚函数是什么”文章能帮助大家解决问题。多态性多态性是面向对象程序设计的关键技术之一,若程序设计
2023-06-29

c++虚函数的作用是什么

C++中的虚函数是一种特殊的成员函数,用于实现多态性。虚函数允许在派生类中重新定义基类中定义的同名函数,从而实现动态绑定。动态绑定允许在运行时根据对象的实际类型来调用正确的函数。虚函数的作用包括:实现多态性:虚函数使得基类指针或引用可以指
2023-10-26

怎么实现C++虚函数表中的虚函数

这篇文章主要介绍“怎么实现C++虚函数表中的虚函数”,在日常操作中,相信很多人在怎么实现C++虚函数表中的虚函数问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么实现C++虚函数表中的虚函数”的疑惑有所帮助!
2023-06-17

C语言函数的调用原理是什么

C语言函数的调用原理是通过栈来实现的。当一个函数被调用时,系统会为该函数分配一块内存空间,这块空间被称为栈帧。栈帧包含了函数的参数、局部变量以及其他与函数执行相关的信息。函数调用时,系统将函数的返回地址(即调用函数后继续执行的位置)压入栈中
2023-09-04

c++虚函数指的是什么

C++的虚函数是一种特殊的成员函数,用于实现多态。多态是面向对象编程的一个重要特性,它允许以统一的方式处理不同类型的对象。虚函数通过在基类中声明并在派生类中重新定义,实现了动态绑定(也称为运行时多态)。当基类指针或引用指向派生类对象时,通
c++虚函数指的是什么
2024-02-29

SSH原理是什么与怎么使用

这篇文章主要介绍“SSH原理是什么与怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SSH原理是什么与怎么使用”文章能帮助大家解决问题。ssh-key密钥常用来管理我们的git仓库,gitla
2023-06-27

C++虚析构函数的作用是什么

C++虚析构函数的作用是确保在通过基类指针删除派生类对象时能正确调用派生类的析构函数,以避免内存泄漏和其他问题。当基类指针指向一个派生类对象并且在删除指针时没有使用虚析构函数时,只会调用基类的析构函数,而不会调用派生类的析构函数,导致派生类
C++虚析构函数的作用是什么
2024-03-11

C++二阶构造模式的原理是什么与怎么使用

这篇文章主要介绍了C++二阶构造模式的原理是什么与怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++二阶构造模式的原理是什么与怎么使用文章都会有所收获,下面我们一起来看看吧。一、构造函数的回顾关于构造
2023-06-30

C++ 函数可以声明为虚函数吗?虚函数的作用是什么?

c++++中的虚函数允许派生类重新定义从基类继承的方法,实现多态。其语法为:在基类中用virtual关键字声明虚函数,在派生类中用override重新定义。通过指针或引用调用虚函数,可实现派生类对象调用基类虚函数。虚函数的主要作用包括:实现
C++ 函数可以声明为虚函数吗?虚函数的作用是什么?
2024-04-19

C++的友元和虚函数是什么

这篇文章主要介绍了C++的友元和虚函数是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++的友元和虚函数是什么文章都会有所收获,下面我们一起来看看吧。友元可以是一个函数,该函数被称为友元函数;友元也可以是
2023-06-17

c++虚函数和纯函数的区别是什么

C++中的虚函数和纯虚函数都是用来实现多态性的机制,但它们有一些不同之处。虚函数是在基类中声明的,可以在派生类中进行重写,它允许在运行时根据对象类型来调用相应的函数。虚函数可以被派生类重写,也可以选择不重写。如果派生类中没有重写虚函数,那
2023-10-26

C++中函数重载定义与原因是什么

小编给大家分享一下C++中函数重载定义与原因是什么,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!引例如果要求你只能通过print函数,即能打印字符串,又能打印一个整型。虽然在C语言中我们可以通过 print_i 和prin
2023-06-29

C# 中get与post的原理是什么

这期内容当中小编将会给大家带来有关C# 中get与post的原理是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。C# get post中post和get的不同之处get与post的区别在于:(对于CG
2023-06-17

C++引用怎么使用及底层原理是什么

本篇内容介绍了“C++引用怎么使用及底层原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!引用引用不是定义一个新变量,而是给已存在的变
2023-06-30

C++虚函数的实现机制是什么

这篇文章主要讲解了“C++虚函数的实现机制是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++虚函数的实现机制是什么”吧!目录1、虚函数简介2、虚函数表简介3、有继承关系的虚函数表剖析
2023-06-20

C++ 函数模板的底层实现原理是什么?

c++++ 函数模板通过部分特化根据不同的类型生成不同的函数代码,优化效率。每次调用模板时,编译器会实例化函数并生成特定于模板参数的特化代码。函数模板提供代码重用、灵活性和性能优化等优势。C++ 函数模板的底层实现原理函数模板是 C++
C++ 函数模板的底层实现原理是什么?
2024-04-24

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录