虚表

在C++的多态机制中,使用了 virtual 关键字声明的函数称之为虚函数,每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚拟函数表(简称:虚表,以下用 vftable表示),表中的每一个元素都指向一个虚函数的地址。

我们都知道在C++中对象生成有两个步骤:

1、分配内存空间

2、调用构造函数

多态机制发生在运行阶段,也就是对象生成阶段。那么问题就来了,虚表(编译阶段生成)是什么时候被写入到对象中的呢?

1、探究虚函数表写入时机

目前有两种假设

  • 假设一:虚表写入发生在在构造函数之前
  • 假设二:虚表写入发生在在构造函数之后

这里设计了一段代码来探究虚表具体的写入时机

#include <iostream>

class Base		//定义基类
{
public:
Base(int a) :ma(a)
{
std::memset(this, 0, sizeof(this));
}
virtual void Show()
{
std::cout << "Base: ma = " << ma << std::endl;
}
protected:
int ma;
}; int main()
{
Base* pb = new Base(10);
pb->Show();
return 0;
}
实验原理:
  • 在构造函数中使用 memset() 函数,把对象中的所有值赋值为0,如果虚表在构造函数之前被写入,将会是以下过程:

    对象开辟空间后,构造函数调用之前,对象中 vfptr==》vftable 虚表已经被写入对象(虚表指针中存入虚表的地址)



    调用构造函数后,std::memset(this, 0, sizeof(this)) 将对象全部赋值为0,vfptr==》NULL



    推测:如果虚表在构造函数之前被写入,那么,pb->Show() 将无法调用,程序崩溃

运行测试:



现在,我们在分析如果虚表在构造函数之后被写入。

那么,在调用构造函数后(ma=0. vfptr==》NULL),虚表会被写入对象,即 vfptr==》vftable 。根据上面的运行结果显示,显然不是这样的。

结论:

虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的 vfptr,然后调用构造函数。即:虚表在构造函数之前写入

2、虚表的二次写入

先别急着下结论,在上面的实验中我们只测试了基类,没有测试派生类。虚表可不只有一张,在它的继承类中也存在一份虚表,因此我们接下来再做一个实验:

#include <iostream>

class Base		//定义基类
{
public:
Base(int a) :ma(a)
{
std::memset(this, 0, sizeof(this));
}
virtual void Show()
{
std::cout << "Base: ma = " << ma << std::endl;
}
protected:
int ma;
};
///////////////////////////////////////////////////////////
// 以下为新添加部分
class Deriver : public Base //派生类
{
public:
Deriver(int b) :mb(b), Base(b) {} // 构造函数什么都不做
void Show()
{
std::cout << "Deriver: mb = " << mb << std::endl;
}
protected:
int mb;
};
////////////////////////////////////////////////////////////
int main()
{
Base* pb = new Deriver(10);
pb->Show();
return 0;
}

这次加上派生类,并且依然让基类的构造中进行 memset() 操作,让我们来看看运行结果:



过程分析:(如下图所示)由于在子类的构造函数中没有做任何事,因此第③步虚表指向并没有改变,最后正常输出Deriver::mb 的内容。



从这里也可以看出多态实现的原理。每个类只有一张虚表,类的对象共享类的虚表,并且通过虚表的二次写入机制,让每个对象的虚表指针都能准确的指向到自己类的虚表,为实现动多态提供支持。


最后在附上一张动多态原理图:



在调用虚函数时,如pb->Show() ,通过对象的vfptr 查询虚表,在虚表中保存着 Deriver::Show() 的函数入口地址,从而实现父类指针访问子类对象的方法。

也就是说,在调用成员函数时会根据调用函数的对象的类型来执行不同的函数。从而实现了去调同一函数,而产生了不同的行为,形成了多种状态的效果。

C++ | 虚表的写入时机的更多相关文章

  1. C++中new和delete的背后( call edx 调用虚表内的第二个函数(析构函数))

    关于 C++中new背后的行为, 以前已经写过一篇了 理解C++中new背后的行为, 但是里面也只是泛泛而谈,没有真凭实据, 下面我们从汇编的角度看C++编译器究竟在背后干了什么?   我们的代码很简 ...

  2. Oracle体系结构详解

    对于一门技术的学习,尤其是像Oracle database这种知识体系极其庞杂的技术来讲,从宏观上了解其体系结构是至关重要的.同时,个人认为,未必是专业DBA人员才需要了解其体系结构(固然对于数据库专 ...

  3. Kafka server.properties配置说明(转)

    原文:https://my.oschina.net/infiniteSpace/blog/312890?p=1 http://www.inter12.org/archives/842 broker.i ...

  4. C++中new和delete的背后

    关于 C++中new背后的行为, 以前已经写过一篇了 理解C++中new背后的行为, 但是里面也只是泛泛而谈,没有真凭实据, 下面我们从汇编的角度看C++编译器究竟在背后干了什么? 我们的代码很简单, ...

  5. InnoDB源码分析--事务日志(一)

    原创文章,转载请注明原文链接(http://www.cnblogs.com/wingsless/p/5705314.html) 在之前的文章<InnoDB的WAL方式学习>(http:// ...

  6. Apache kafka原理与特性(0.8V)

    前言: kafka是一个轻量级的/分布式的/具备replication能力的日志采集组件,通常被集成到应用系统中,收集"用户行为日志"等,并可以使用各种消费终端(consumer) ...

  7. kafka中server.properties配置文件参数说明

    转自:http://blog.csdn.net/lizhitao/article/details/25667831 参数 说明(解释) broker.id =0 每一个broker在集群中的唯一表示, ...

  8. MySQL数据库优化总结

    对于一个以数据为中心的应用,数据库的好坏直接影响到程序的性能,因此数据库性能至关重要.一般来说,要保证数据库的效率,要做好以下四个方面的工作:数 据库设计.sql语句优化.数据库参数配置.恰当的硬件资 ...

  9. 【转载】Apache kafka原理与特性(0.8V)

    http://blog.csdn.net/xiaolang85/article/details/37821209 前言: kafka是一个轻量级的/分布式的/具备replication能力的日志采集组 ...

随机推荐

  1. vmware扩容centos根目录

    在vmware中编辑,给磁盘扩容 在centos中使用命令fdisk /dev/sda 输入n创建新分区 输入p创建主分区 回车,默认分区号 回车,默认起始扇区 回车,默认last扇区 输入t,改变分 ...

  2. CentOS安装时,软件选择(Software Selection)项介绍

    要指定软件包将被安装,选择软件时选择安装摘要屏幕.包组分为基础环境.这些环境是预先定义的一组具有特定用途的软件包:例如,在虚拟化主机环境中包含的一组所需的系统上运行的虚拟机软件程序包.只有一个软件环境 ...

  3. laravel 框架 下拉分页

    <!doctype html><html lang="en"><head> <meta charset="UTF-8" ...

  4. ASP.NET Core 6框架揭秘实例演示[21]:如何承载你的后台服务

    借助 .NET提供的服务承载(Hosting)系统,我们可以将一个或者多个长时间运行的后台服务寄宿或者承载我们创建的应用中.任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载,A ...

  5. NSView subview blocking drag/drop event

    原文链接 近期在Mac项目中有一个处理鼠标拖拽事件的需求, 大致处理流程是这样的: 从 NSView 继承得到一个子类 覆盖处理拖拽事件相关方法 注册拖拽事件 开始的时候一切都很正常,直到某次发现拖拽 ...

  6. Java GUI 实现发送邮件以及附件

    实现代码: 注意点: 需要的jar包:JavaMail API 和Java Activation Framework (JAF) ,下载可参考菜鸟教程 默认使用QQ邮箱发送,需要设置授权,设置--&g ...

  7. 关于OAuth2.0 Authorization Code + PKCE flow在原生客户端(Native App)下集成的一点思考

    写在前面 前几天看了园友的一篇文章被广泛使用的OAuth2.0的密码模式已经废了,放弃吧 被再次提起: Implicit Flow Password Grant,均已被标记为Legacy,且OAuth ...

  8. Django安装和web框架原理

    Django安装和web框架原理 在PyCharm中安装 在cmd中输入 django-admin startproject 项目名 如果报错 不是内部或外部命令,也不是可运行的程序 需要添加环境变量 ...

  9. 6月15日 python学习总结 Django模板语言相关内容

    Django模板语言相关内容   Django模板系统 官方文档 常用语法 只需要记两种特殊符号: {{  }}和 {% %} 变量相关的用{{}},逻辑相关的用{%%}. 变量 {{ 变量名 }} ...

  10. eclipse新建maven项目:'Building' has encountered a problem. Errors occurred during the build.

    二.eclipse 新建maven 项目报错(因为没有配置maven环境) 1.问题: ① 出现的问题1: Could not calculate build plan:Plugin org.apac ...