概述 首先,相较于C语言,C++语言并没有额外增加内存消耗(确切说,在没有虚函数情况下)。 对于一个C++类对象,每个对象有独立的数据成员(非static),但是内存中成员函数只有一份,该类的所有对象共享成员函数。 编译器在编译阶段,进行函数的重构,即将成员函数进行非成员化。 通过将this指针作为函数的第一个参数,通过this指针即可以找到对象的数据成员 使用GDB调试 C++ 虚函数 class Base { public: int a; 构造函数与虚函数表 虚函数表创建时机是在编译期间。 编译期间编译器就为每个类确定好了对应的虚函数表里的内容。 所以在程序运行时,编译器会把虚函数表的首地址赋值给虚函数表指针,所以,这个虚函数表指针就有值了。 ?
概述 为实现C++多态,C++使用了一种动态绑定技术,这个技术核心是虚函数表 类的虚表 一个类继承包含虚函数的基类,那么这个类也有自己的虚表。 虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针,普通的函数即非虚函数,其调用不需要经过虚表。 虚表指针 续表是属于类的,而不是属于某个具体的对象。 一个类只需要一个虚表。同一个类的所有对象都使用同一个虚表。 对象内部包含一个指向虚表的指针,用来指向自己的虚表。 动态绑定 对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数。
参考链接: C++函数覆盖 C++的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 虚函数表中只存有一个虚函数的指针地址,不存放普通函数或是构造函数的指针地址。 只要有虚函数,C++类都会存在这样的一张虚函数表,不管是普通虚函数 亦或 是 纯虚函数,亦或是 派生类中隐式声明的这些虚函数都会 生成这张虚函数表。 虚函数表创建的时间:在一个类构造的时候,创建这张虚函数表,而这个虚函数表是供整个类所共有的。虚函数表存储在对象最开始的位置。 首先了解下虚函数表:虚函数表其实就是函数指针的地址。 但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
一、概述 为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表(下文简称虚表)。本文介绍虚函数表是如何实现动态绑定的。 二、类的虚表 每个包含了虚函数的类都包含一个虚表。 四、动态绑定 说到这里,大家一定会好奇C++是如何利用虚表和虚表指针来实现动态绑定的。我们先看下面的代码。 可以把以上三个调用函数的步骤用以下表达式来表示: (*(p->__vptr)[n])(p) 可以看到,通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。 C++通过虚函数表,实现了虚函数与对象的动态绑定,从而构建了C++面向对象程序设计的基石。 参考资料 《C++ Primer》第三版,中文版,潘爱民等译 http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/ 侯捷《C++最佳编程实践
参考链接: C++虚函数 C++的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 虚函数表中只存有一个虚函数的指针地址,不存放普通函数或是构造函数的指针地址。 只要有虚函数,C++类都会存在这样的一张虚函数表,不管是普通虚函数 亦或 是 纯虚函数,亦或是 派生类中隐式声明的这些虚函数都会 生成这张虚函数表。 虚函数表创建的时间:在一个类构造的时候,创建这张虚函数表,而这个虚函数表是供整个类所共有的。虚函数表存储在对象最开始的位置。 首先了解下虚函数表:虚函数表其实就是函数指针的地址。 但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制。 比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。 关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。 言归正传,让我们一起进入虚函数的世界。 虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
C++虚函数表和对象存储 C++中的虚函数实现了多态的机制,也就是用父类型指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种技术可以让父类的指针有“多种形态”,这也是一种泛型技术,也就是使用不变的代码来实现可变的算法 C++通过继承和虚函数来实现多态性,虚函数是通过一张虚函数表实现的,虚函数表解决了继承、覆盖、添加虚函数的问题,保证其真实反应实际的函数 不太熟悉的朋友,以下内容可能看的很懵,个人建议上下来回看 函数表原理简述 C++实现虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员保存了一个指针,这个指针叫虚表指针(vptr),它指向一个虚函数表(virtual function table, vtbl) 虚函数表就像一个数组 找到虚函数表 C++的编译器会保证虚函数表的指针存在于对象实例中最前面的位置(为了保证取虚函数表有最高的性能,在有多层继承或是多重继承的情况下),这意味着我们通过对象实例的地址得到这张虚函数表的地址, 因为在多继承下,虚函数表存储方式发生了点变化,我们之前说到C++编译器在对象内加入了一个隐藏成员,现在你可以理解为,在多继承时加入了多个隐藏成员,也就是说我们现在有多个虚函数表,具体排列方式如下图: ?
2.1.5override和final关键字 从上面可以看出,C++对虚函数重写的要求比较严格,但是有些情况下由于疏忽,比如上面由于函数名写错导致无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来 用图来表示就如上图所示,虚函数表这里先简单提一下,下面会详细讲。 虚函数表又叫虚函数指针数组或者虚表,里面存的就是虚函数的指针。 (这个C++并没有进行规定,各个编译器自行定义的,vs系列编译器会再后面放个0x00000000标记,g++系列编译不会放) 6.虚函数存在哪的? 虚函数和普通函数一样的,编译好后是一段指令,都是存在代码段的,只是虚函数的地址又存到了虚表中。 7.虚函数表在哪儿呢?这个问题并没有标准答案,c++并没有规定。 这里主要就是讲一下如何找到虚函数表在哪儿: 再找之前呢我们先得到几个常见的区域的地址,好拿来比较。 而找虚函数表的难点就在于_vfptr我们拿不出来,就无法拿到里面所保存的虚函数表的地址。
-------------百度百科 虚函数表存在的位置 由于虚函数表是由编译器给我们生成的,那么编译器会把虚函数表安插在哪个位置呢? 可以观察到结果是不同的,而且正好相差了4个字节,由此可见,编译器把生成的虚函数表放在了最前面。 获取虚函数表 既然虚函数表是真实存在的,那么我们能不能想办法获取到虚函数表呢? 我们来分析这两个类的虚函数表,对于基类的虚函数表其实和上面所说的虚函数表是一样的,有自己的虚函数指针,并指向自己的虚函数表,重点是在于派生类的虚函数表是什么样子的,它的样子如下图所示: ? 那么Derive的虚函数表就是继承了Base1的虚函数表,然后自己的虚函数放在后面,因此这个虚函数表的顺序就是基类的虚函数表中的虚函数的顺序+自己的虚函数的顺序。 由图可以看出,在第一个虚函数表中首先继承了Base1的虚函数表,然后将自己的虚函数放在后面,对于第二个虚函数表中,继承了Base2的虚函数表,由于在Derive类中有一个虚函数D覆盖了Base2的虚函数
虚函数表是编译器生成的,程序运行时被载入内存。一个类的虚函数表中列出了该类的全部虚函数地址。例如,在上面的程序中,类 A 对象的存储空间以及虚函数表(假定类 A 还有其他虚函数)如图 1 所示。 图1:类A对象的存储空间以及虚函数表 类 B 对象的存储空间以及虚函数表(假定类 B 还有其他虚函数)如图 2 所示。 ? 图2:类B对象的存储空间以及虚函数表 多态的函数调用语句被编译成根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的一系列指令。 2) 根据虚函数表的地址找到虚函数表,在其中查找要调用的虚函数的地址。不妨认为虚函数表是以函数名作为索引来查找的,虽然还有更高效的查找方法。 虚函数表、各个对象中包含的 4 个字节的虚函数表的地址都是空间上的额外开销;而查虚函数表的过程则是时间上的额外开销。
前言 在 C++ 面向对象编程中,虚函数与虚函数表是实现多态的核心机制,而继承关系(尤其是单继承与多继承)会显著影响虚函数表的结构。 理解虚函数表的内存布局和访问机制,不仅是深入掌握 C++ 多态的关键,也是应对面试与实际开发中复杂问题的基础。下面就让我们正式开始学习吧! 一、虚函数表基础:从单继承说起 虚函数表(Virtual Table,简称 vtable)是 C++ 编译器在编译阶段为包含虚函数的类生成的特殊数据结构,用于存储虚函数的地址。 下面给大家推荐两篇文章,如果感兴趣的话可以参考借鉴一下~ https://coolshell.cn/articles/12165.html(C++虚函数表解析) https://coolshell.cn /articles/12176.html(C++对象的内存布局) 总结 继承与多态的常见问题多围绕虚函数重写、指针转换、内存布局展开,掌握这些知识点不仅能帮助我们应对面试,更能在实际开发中避免内存泄漏
关键词:虚函数,虚表,虚表指针,动态绑定,多态 一、概述 为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表(下文简称虚表)。本文介绍虚函数表是如何实现动态绑定的。 四、动态绑定 说到这里,大家一定会好奇C++是如何利用虚表和虚表指针来实现动态绑定的。我们先看下面的代码。 可以把以上三个调用函数的步骤用以下表达式来表示: (*(p->__vptr)[n])(p) 可以看到,通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。 C++通过虚函数表,实现了虚函数与对象的动态绑定,从而构建了C++面向对象程序设计的基石。 参考资料 《C++ Primer》第三版,中文版,潘爱民等译 http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/ 侯捷《C++最佳编程实践
多态的底层实现逻辑 编译器找对应虚函数的步骤: this指针->vptr指针->vtable表(虚函数表)->对应的虚函数(通过指针偏移,vptr就是虚函数表的起始指针,函数指针在虚函数表占的位置也是固定的 (这和编译器寻找虚函数完全不同) 重写/覆盖 在Dog和Cat类中,都有一张虚函数表,他们继承父类Anima的虚函数表,因为在Cat和Dog中对soeak函数都进行了重写,所以他们自己的虚函数表的指针就发生了变化 一个非静态成员函数函数在前面加上virtual就变成了虚函数。 因为虚函数是在虚函数表中的,虚函数表要靠vptr才能找到,vptr要这个对象的this指针才能找到。 静态成员函数是不需要靠 虚函数的分类有:纯虚函数和普通虚函数。 有纯虚函数的类不能进行实例化,必须在子类重写以后才能实例化。 1.什么情况下会有虚函数表? 当一个类有虚函数的时候,就会有虚函数表。 在C++语言中,函数签名没有包含返回值(但是有的编程语言中把返回值也作为函数签名的一部分),所以如果一个函数的名称相同,参数列表相同。只有返回值不同,他们的函数签名是一样的。编译器就不能区分他们了。
,多态的核心就要靠着这个虚函数表来实现的 总结:(很重要) 普通调用时编译时确定地址 多态调用时运行时到指向的对象中找到这个虚函数表指针,然后通过虚函数表指针找到虚函数表,在虚函数表中找到相应的虚函数, 或者是多态调用时运行是到指向的对象里面找地址,所以指向谁就去谁的里面找这个虚函数表指针,然后通过虚函数表指针找到虚函数表,在虚函数表中找到相应的虚函数,然后去调用这个虚函数,指向基类调用基类中的虚函数, /覆盖,这个指针或者引用指向谁就去谁的里面找到这个虚函数指针,通过这个虚函数指针找到这个虚函数表,底层原理是依靠对象当中存放的一张虚函数表,不同的虚函数表中存放的是不同的虚函数,基类中存放的是基类的虚函数 基类对象的虚函数表中存放基类所有虚函数的地址。 这个问题严格说并没有标准答案,C++标准并没有规定,但是我们写下面的代码可以对比验证一下。
, 由 " 虚函数表 " 实现 ; " 虚函数表 " , 英文名称为 " Virtual Function Table " , 简称 Vtable ; C++ 编译器 通过将 虚函数指针 放入 基类 的 虚函数表中 , 实现在 运行时 根据实际对象的类型 来调用对应的 virtual 虚函数 ; 虚函数表 是由 C++ 编译器 自动维护的 , 对 程序员 透明 ; 3、虚函数表工作机制 " 虚函数表 " 由 C++ 编译器 负责 创建 与 维护 , 被 virtual 关键字 修饰的 虚函数 , 会自动 被 C++ 编译器 存储到 " 虚函数表 " 中 ; 虚函数表 创建 : 在 类 中使用 virtual 关键字 声明 虚函数 时 , C++ 编译器 会自动为该类生成 " 虚函数表 " ; 生成虚函数表的前提是 至少有 1 个虚函数 ; 如果 没有虚函数 , 就不会生成虚函数表 ; 如果 类 中有 virtual vtable ; C++ 编译器 编译 代码时 , 会自动为该类 添加 一个 vptr 指针 成员变量 , 该指针 会指向 虚函数表 ; 5、虚函数表运行时机制 " 虚函数表 " 在 C++ 编译器 编译
virtual ~Student() { cout << "~Student()" << endl; } }; // 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函 多态的原理 4.1 虚函数表 // 这里常考一道笔试题:sizeof(Base)是多少? 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。 单继承和多继承关系中的虚函数表 5.1 单继承中的虚函数表 class Base { public: virtual void func1() { cout << "Base::func1" << 所以菱形继承、菱形虚拟继承我们的虚表我们就不看了,一般我们也不需要研究清楚,因为实际中很少用 C++ 虚函数表解析 | 酷 壳 - CoolShell C++ 对象的内存布局 | 酷 壳 - CoolShell
在正式讨论虚函数前,我们需要明确c++的设计思想——零成本抽象 对于下面的这个类 class A { public: int x; }; 这个类的大小为4,也就是一个int的大小。 show的位置 printf("%p\n", &A::show); printf("%p\n", &B::show); 输出 00007FF75D8A152D 00007FF75D8A152D 我们整个带虚函数的类 "A b()" << endl; } virtual void c() { cout << "A c()" << endl; } int x, y; }; 大小为16 也就是,只要有虚函数 func fa = (func)arr[0]; func fb = (func)arr[1]; func fc = (func)arr[2]; fa(); fb(); fc(); 此时我们就指向了虚函数 评论区有人提了个问题 如果我们B中有个新的虚函数,然后我们 A∗a=newB 是否可以访问到 class A { public: virtual void a() { cout << "A a
-------------百度百科 虚函数表存在的位置 由于虚函数表是由编译器给我们生成的,那么编译器会把虚函数表安插在哪个位置呢? 获取虚函数表 既然虚函数表是真实存在的,那么我们能不能想办法获取到虚函数表呢? 我们来分析这两个类的虚函数表,对于基类的虚函数表其实和上面所说的虚函数表是一样的,有自己的虚函数指针,并指向自己的虚函数表,重点是在于派生类的虚函数表是什么样子的,它的样子如下图所示: image.png 那么Derive的虚函数表就是继承了Base1的虚函数表,然后自己的虚函数放在后面,因此这个虚函数表的顺序就是基类的虚函数表中的虚函数的顺序+自己的虚函数的顺序。 由图可以看出,在第一个虚函数表中首先继承了Base1的虚函数表,然后将自己的虚函数放在后面,对于第二个虚函数表中,继承了Base2的虚函数表,由于在Derive类中有一个虚函数D覆盖了Base2
-------------百度百科 虚函数表存在的位置 由于虚函数表是由编译器给我们生成的,那么编译器会把虚函数表安插在哪个位置呢? 获取虚函数表 既然虚函数表是真实存在的,那么我们能不能想办法获取到虚函数表呢? 我们来分析这两个类的虚函数表,对于基类的虚函数表其实和上面所说的虚函数表是一样的,有自己的虚函数指针,并指向自己的虚函数表,重点是在于派生类的虚函数表是什么样子的,它的样子如下图所示: 那么Derive的虚函数表就是继承了Base1的虚函数表,然后自己的虚函数放在后面,因此这个虚函数表的顺序就是基类的虚函数表中的虚函数的顺序+自己的虚函数的顺序。 由图可以看出,在第一个虚函数表中首先继承了Base1的虚函数表,然后将自己的虚函数放在后面,对于第二个虚函数表中,继承了Base2的虚函数表,由于在Derive类中有一个虚函数D覆盖了Base2的虚函数
如果 Swift 通过虚函数表跳表的方式来实现方法调用,那么可以借助修改虚函数表来实现方法替换。即将特定虚函数表的函数地址修改为要替换的函数地址。 但是由于虚函数表不包含地址与符号的映射,我们不能像 Objective-C 那样根据函数的名字获取到对应的函数地址,因此修改 Swift 的虚函数是依靠函数索引来实现的。 ▐ 3.2 虚函数表的访问 虚函数表的访问也是动态调用的一种形式,只不过是通过访问虚函数表的方式进行调用。 思考 ---- 既然基于虚函数表的派发形式也是一种动态调用,那么是不是以为着只要我们修改了虚函数表中的函数地址,就实现了函数的替换? 5. 但是这并没有结束,因 为虚函数表与消息发送有所不同,虚函数表中并没有任何函数名和函数地址的映射,我们只能通过偏移来修改函数地址。