C++编程揭秘:虚表机制与ABI兼容性的实例剖析

前言:
假设你的应用程序引用的一个库某天更新了,虽然 API 和调用方式基本没变,但你需要重新编译你的应用程序才能使用这个库,那么一般说这个库是源码兼容(Source compatible);反之,如果不需要重新编译应用程序就能使用新版本的库,那么说这个库跟它之前的版本是二进制兼容的(Binary compatible)。
👉👉👉
而影响ABI兼容中,最重要的部分涉及到虚表机制,这块我们重点来谈下它们之间的关系。

文章目录

      • 虚表生成
      • 子类的虚表
      • C++ 类虚表中函数顺序规则
      • 搞清楚虚表有什么用?
      • 导出DLL注意事项
      • C++ 虚析构函数在虚表中位置说明
      • 更多ABI 相关文章

虚表生成

C++的类只要有一个虚函数,就会生成一张虚表:

class A
{
};

class B
{
public:
	virtual void vfunc1();
}
sizeof(A) = 1	// 空类1个字节用于地址定位
sizeof(B) = 4	// 有虚表指针,占sizeof(void*)字节

子类的虚表

Visual Studio 可以使用自带的命令行工具查看类的内存布局。在 Visual Studio 2022 中是如下工具:

在这里插入图片描述

命令是:cl /d1 reportSingleClassLayout<ClassName> xxx.cpp

例如:cl /d1 reportSingleClassLayoutA demo.cpp 即,在 demo.cpp 中查看 class A 的内存布局。

class A
{
public:
   virtual void vfunc1();
private:
   int a;
};

class B
{
public:
   virtual void vfunc2();
private:
   int b;
};

class C1 : public A
{
public:
   virtual void vfunc3();
private:
   int c;
};

class C2 : public A, public B
{
public:
   virtual void vfunc3();
private:
   int c;
};

class C1 的内存布局是:

class C1        size(12):
        +---
 0      | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
 8      | c
        +---

C1::$vftable@:
        | &C1_meta
        |  0
 0      | &A::vfunc1
 1      | &C1::vfunc3

class C2 的内存布局是:

class C2        size(20):
        +---
 0      | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
 8      | +--- (base class B)
 8      | | {vfptr}
12      | | b
        | +---
16      | c
        +---

C2::$vftable@A@:
        | &C2_meta
        |  0
 0      | &A::vfunc1
 1      | &C2::vfunc3

C2::$vftable@B@:
        | -8
 0      | &B::vfunc2

C++ 类虚表中函数顺序规则

  1. 从基类开始,按照申明顺序每遇到一个不是重写的虚函数,就记录在表中
  2. 如果有重载,则提前重载的虚函数
  3. 依次循环遍历子类,如果遇到重写,则替换相应的虚函数

举例:

class A
{
public:
	virtual void vfunc1() = 0;
	virtual void vfunc2() = 0;
	virtual void vfunc1(int x) = 0;
	virtual void vfunc3() = 0;
    void vfunc4();
    void vfunc4(int x);
	virtual void vfunc1(int x, int y) = 0;
};

class B : public A
{
public:
	virtual void vfunc1(int x) = 0;
	virtual void vfunc4() = 0;
    void vfunc5();
	virtual void vfunc2(int x) = 0;
}

请问B的虚表是应该是什么样的?

  1. 遍历A中的虚函数

    void A::vfunc1();
    

    由于 vfunc1 有两个重载,按照第 2 条规则,依次提前重载函数:

    void A::vfunc1();
    void A::vfunc1(int x);
    void A::vfunc1(int x, int y);
    
  2. 继续遍历A中的虚函数

    void A::vfunc1();
    void A::vfunc1(int x);
    void A::vfunc1(int x, int y);
    void A::vfunc2();
    void A::vfunc3();
    
  3. 由于B 重写了 Avoid vfunc1(int x) 函数,所以将表中对应的函数替换

    void A::vfunc1();
    void B::vfunc1(int x);
    void A::vfunc1(int x, int y);
    void A::vfunc2();
    void A::vfunc3();
    
  4. 添加 B::vfunc4() 到虚表中

    void A::vfunc1();
    void B::vfunc1(int x);
    void A::vfunc1(int x, int y);
    void A::vfunc2();
    void A::vfunc3();
    void B::vfunc4();
    
  5. 由于 B::vfunc2(int x) 没有重写A中的函数,按照规则 1 添加到虚表中

    void A::vfunc1();
    void B::vfunc1(int x);
    void A::vfunc1(int x, int y);
    void A::vfunc2();
    void A::vfunc3();
    void B::vfunc4();
    void B::vfunc2(int x);
    

在这里插入图片描述
在这里插入图片描述

搞清楚虚表有什么用?

答:为了ABI兼容

举例:

某工程师写了这样一个 SDK:

// awesome.h
class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
};

extern "C" {

// 创建SDK实例
IAwesomeSDK *createAwesomeInstance();

// 销毁SDK实例
void destroyAwesomeInstance();

} // extern "C"


// 二次开发用户这样对其进行使用:

// demo.cpp
int main(int argc, char **argv)
{
	IAwesomeSDK *sdk = createAwesomeInstance();
	sdk->foo();
	sdk->bar();
	destroyAwesomeInstance();
	return 0;
}

如果保证新发布的动态库可以兼容之前的程序(集成DLL的程序不需要重新编译,就可以使用新DLL),那么动态库中添加功能需要注意:

  1. 只能在类最后添加新的虚函数

    class IAwesomeSDK
    {
    public:
    	virtual void feature1() = 0;		// 错误
    	virtual void foo() = 0;
    	virtual void bar(int x) = 0;
    };
    
  2. 添加的新函数可以与旧函数重名(重载)

    class IAwesomeSDK
    {
    public:
    	virtual void foo() = 0;
    	virtual void bar(int x) = 0;
    	virtual void bar() = 0;				// 错误
    };
    
  3. 可以修改旧函数的签名(参数,返回值,限定符等)

    class IAwesomeSDK
    {
    public:
    	virtual void foo(int x = 0) = 0;	// 错误
    	virtual void bar(int x) = 0;
    };
    
  4. 可以重新排序旧函数

    class IAwesomeSDK
    {
    public:
    	virtual void bar(int x) = 0;		// 错误
    	virtual void foo() = 0;				// 错误
    };
    

这时你要添加一个新功能,还希望旧程序可以不重新编译替换新DLL,你可以这么做:

class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
	virtual void feature() = 0;			// 正确
};

导出DLL注意事项

  1. 申请和释放内存保持在同一模块。

  2. 最好不要在接口处使用STL库,除非编译器选项一致、STL实现一致、系统平台一致。

class IAwesomeSDK
{
public:
	virtual void foo() = 0;
	virtual void bar(int x) = 0;
	virtual std::string feature() = 0;			// 错误,模块内申请,模块外释放
};

C++ 虚析构函数在虚表中位置说明

先说结论:如果类中含有虚析构函数,其受约束和普通虚函数一致:

  1. 从基类开始,按照申明顺序每遇到一个不是重写的虚函数,就记录在表中
  2. 如果有重载,则提前重载的虚函数
  3. 依次循环遍历子类,如果遇到重写,则替换相应的虚函数

数据测试如下:(环境:Visual Studio 2022,默认配置)

  • 测试项1:没有虚析构函数时,虚表中的排布情况如下图:
    在这里插入图片描述

    在这里插入图片描述

  • 测试项2:虚析构函数位于类首时:
    在这里插入图片描述

    在这里插入图片描述

  • 测试项3:虚析构函数位于有重载的虚函数前面时:
    在这里插入图片描述

    在这里插入图片描述

  • 测试项4:虚析构函数位于非重载的虚函数前面时:
    在这里插入图片描述

    在这里插入图片描述

更多ABI 相关文章

  • 【1】C++ 编程必看!超万字深度解析API与ABI兼容性的关键问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/646608.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CAN总线简介

1. CAN总线概述 1.1 CAN定义与历史背景 CAN&#xff0c;全称为Controller Area Network&#xff0c;是一种基于消息广播的串行通信协议。它最初由德国Bosch公司在1983年为汽车行业开发&#xff0c;目的是实现汽车内部电子控制单元&#xff08;ECUs&#xff09;之间的可靠通信。…

批量漏洞挖掘思路小结

漏洞挖掘是指对应用程序中未知漏洞的探索&#xff0c;通过综合应用各种技术和工具&#xff0c;尽可能地找出其中的潜在漏洞。一般情况下漏洞挖掘针对单一的应用系统&#xff0c;通过端口扫描、目录扫描、文件扫描等方式对其安全性进行评估&#xff0c;而本文主要针对Nday和1day…

软考结束。有什么要说的

1. 竟然是机试&#xff0c;出乎我意料。是 考试机构觉得笔试成本高了么。这次的考试是机试&#xff0c;相比以往有所不一样。感言是不是以后都会在固定地点考试也说不准。 2. 遇到年轻人。 这次旁边的一个女同学第一次参加&#xff0c;还像我询问了一些关于软考的事。我是有…

【设计模式】JAVA Design Patterns——Command(事务模式)

&#x1f50d;目的 将请求封装为对象&#xff0c;从而使你可以将具有不同请求的客户端参数化&#xff0c;队列或记录请求&#xff0c;并且支持可撤销操作。 &#x1f50d;解释 真实世界例子 有一个巫师在地精上施放咒语。咒语在地精上一一执行。第一个咒语使地精缩小&#xff0…

从零实现Llama3中文版

1.前言 一个月前&#xff0c;Meta 发布了开源大模型 llama3 系列&#xff0c;在多个关键基准测试中优于业界 SOTA 模型&#xff0c;并在代码生成任务上全面领先。 此后&#xff0c;开发者们便开始了本地部署和实现&#xff0c;比如 llama3 的中文实现、llama3 的纯 NumPy 实现…

06中间件RTOS/CP

Autosar CP 操作系统详解-CSDN博客 1. 什么是RTOS &#xff1f; RTOS&#xff0c;英文全称是 Real-time Operation System&#xff0c;中文就是 实时操作系统&#xff0c;又称及时操作系统。 实时操作系统&#xff0c;是指当外界事件或数据产生时&#xff0c;能够接受并以足…

【HMGD】STM32/GD32 CAN通信

各种通信协议速度分析 协议最高速度(btis/s)I2C400KCAN1MCAN-FD5M48510MSPI36M CAN协议图和通信帧 CubeMX CAN配置说明 CAN通信波特率 APB1频率 / 分频系数 /&#xff08;BS1 BS2 同步通信段&#xff09;* 1000 ​ 42 / 1 / (111) * 1000 ​ 14,000 KHz ​ 1400000…

【Java面试】二、Redis篇(中)

文章目录 1、Redis持久化1.1 RDB1.2 AOF1.3 RDB与AOF的对比 2、数据过期策略&#xff08;删除策略&#xff09;2.1 惰性删除2.2 定期删除 3、数据淘汰策略4、主从复制4.1 主从全量同步4.2 增量同步 5、哨兵模式5.1 服务状态监控5.2 哨兵选主规则5.3 哨兵模式下&#xff0c;Redi…

Android ListView鼠标模式下ListView回滚问题

概述 在 Android 应用程序中&#xff0c;ListView 是一种常用的控件&#xff0c;用于显示可滚动列表数据。然而&#xff0c;当在鼠标操作模式下使用 ListView 时&#xff0c;可能会遇到一个问题&#xff1a;点击列表项时&#xff0c;列表会回滚到指定位置&#xff0c;这可能会导…

c语言IO

前言 老是忘记c语言IO操作&#xff0c;故写个文章记录一下 打开文件 fopen FILE *fopen(const char *path, const char *mode);mode 返回值 如果文件成功打开&#xff0c;fopen 返回一个指向 FILE 结构的指针。如果文件打开失败&#xff08;例如&#xff0c;因为文件不存…

CMS Full GC流程以及调优配置

个人博客 CMS Full GC流程以及调优配置 | iwts’s blog CMS CMS 收集器是以实现最短 STW 时间为目标的收集器&#xff0c;所以对于偏业务的后台开发而言&#xff0c;基本上都无脑选CMS了。 多线程收集器&#xff0c;工作在老年代&#xff0c;采用标记清除算法。比较特殊&am…

leetcode-顺时针旋转矩阵-111

题目要求 思路 1.假设现在有一个矩阵 123 456 789 2.我们可以根据19这个对角线将数据进行交换&#xff0c;得到矩阵 147 258 369 3.然后将矩阵每一行的数据再翻转&#xff0c;得到矩阵 741 852 963 代码实现 class Solution { public:vector<vector<int> > rot…

贪心-ACW803区间合并-XMUOJ力量碎片合并

题目 思路 附上几个参考链接 for(auto i : v)遍历容器元素_for auto 遍历-CSDN博客 C pair的基本用法总结&#xff08;整理&#xff09;_c pair用法-CSDN博客 使用 sort 实现自定义排序 - AcWing 话不多说&#xff0c;直接上代码 代码 /* ACW803区间合并-XMUOJ力量碎片合…

【C++】二分查找算法:x的平方根

1.题目 2.算法思路 看到题目可能不容易想到二分查找。 这题考察我们对算法的熟练程度。 二分查找的特点&#xff1a;数组具有二段性(不一定有序)。 题目中没有数组&#xff0c;我们可以造一个从0到x的数组&#xff0c;然后利用二分查找找到对应的值即可。 3.代码 class S…

Android 布局中@NULL的使用和代码实现方式详解

文章目录 1、使用场景2、示例代码实现2.1、移除背景2.2 、移除文本2.3、移除布局宽度或高度2.4、移除提示文本2.5、移除图像资源 3、综合示例3.1、布局文件 activity_main.xml3.2、主活动文件 MainActivity.java3.4、资源文件3.5、运行结果 4、优点5、缺点6、综合分析6.1、适用…

MySQL数据库基础:使用、架构、SQL语句、存储引擎

文章目录 什么是数据库CS模式 基本使用安装链接服务器服务器、数据库、表关系简单使用数据库在Linux下的体现 MySQL架构连接器层客户端层服务层存储引擎层物理存储层 SQL分类存储引擎 什么是数据库 mysql&#xff1a;数据库服务的客户端mysqld&#xff1a;数据库服务的服务器端…

BGP策略实验(路径属性和选路规则)

要求&#xff1a; 1、使用preval策略&#xff0c;确保R4通过R2到达192.168.10.0/24 2、使用AS Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 3、配置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 4、使用Local Preference策略&#xff0c;确保R1通过R2到达19…

移动云以深度融合之服务,令“大”智慧贯穿云端

移动云助力大模型&#xff0c;开拓创新领未来。 云计算——AI模型的推动器。 当前人工智能技术发展的现状和趋势&#xff0c;以及中国在人工智能领域的发展策略和成就。确实&#xff0c;以 ChatGPT 为代表的大型语言模型在自然语言处理、文本生成、对话系统等领域取得了显著的…

深度学习中的多GPU训练(Pytorch 20)

一 多GPU训练 下面详细介绍如何从零开始并行地训练网络&#xff0c;这里需要运用小批量随机梯度下降算法。后面我还讲介绍如何使用高级API并行训练网络。 我们从一个简单的计算机视觉问题和一个稍稍过时的网络开始。这个网络有多个卷积层和汇聚层&#xff0c;最后可能 有几个…

构建智慧科技园区的系统架构:数字化驱动未来创新

随着科技的不断进步和数字化转型的加速推进&#xff0c;智慧科技园区已成为当今城市发展的重要组成部分。在智慧科技园区建设中&#xff0c;系统架构的设计和实施至关重要&#xff0c;对于提升园区管理效率、优化资源利用、促进创新发展具有重要意义。 一、智慧科技园区系统架构…