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

news2024/10/6 18:32:28

前言:
假设你的应用程序引用的一个库某天更新了,虽然 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兼容性的关键问题

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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…

不同网段的通信过程

这里的AA和HH指的是mac地址&#xff0c;上面画的是路由器 底下的这个pc1&#xff0c;或者其他的连接在这里的pc&#xff0c;他们的默认网关就是路由器的这个192.168.1.1/24这个接口 来看看通信的过程 1、先判断&#xff08;和之前一样&#xff09; 2、去查默认网关&#xf…

【MySQL】库的基础操作

&#x1f30e;库的操作 文章目录&#xff1a; 库的操作 创建删除数据库 数据库编码集和校验集 数据库的增删查改       数据库查找       数据库修改 备份和恢复 查看数据库连接情况 总结 前言&#xff1a;   数据库操作是软件开发中不可或缺的一部分&#xff0…

Hibernate

主流ORM框架Object Relation Mapping对象关系映射&#xff0c;将面向对象映射成面向关系。 如何使用 1、导入相关依赖 2、创建Hibernate配置文件 3、创建实体类 4、创建实体类-关系映射文件 5、调用Hibernate API完成操作 具体操作 1、创建 Maven工程&#xff0c;在pom.xml中导…

应用程序图标提取

文章目录 [toc]提取过程提取案例——提取7-zip应用程序的图标 提取过程 找到需要提取图标的应用程序的.exe文件 复制.exe文件到桌面&#xff0c;并将复制的.exe文件后缀改为.zip 使用解压工具7-zip解压.zip文件 在解压后的文件夹中&#xff0c;在.rsrc/ICON路径下的.ico文件…

01.并发编程简介

1 什么是并发编程 所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。 2 为什么我们要学习并发编程&#xff1f; 最直白的原因就是因为面试需要&#xff0c;大厂的 Java 岗的并发编程能力属于标配。 而在非大厂…

vscode远程连接Ubuntu mysql服务器

注意&#xff1a;刚开始使用root用户死活连接不上&#xff0c;可能就是root用户没有权限的问题&#xff0c;可以尝试创建一个新的数据库用户&#xff0c;授予权限进行连接 ubuntu安装mysql 创建新用户 执行&#xff1a;sudo apt-get install mysql-server安装服务器(yum) 执…

【排序算法】选择排序以及需要注意的问题

选择排序的基本思想&#xff1a;每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完 。 第一种实现方法&#xff1a; void SelectSort(int* arr, int n) {for (int j 0…

Python Beautiful Soup 使用详解

大家好&#xff0c;在网络爬虫和数据抓取的领域中&#xff0c;Beautiful Soup 是一个备受推崇的 Python 库&#xff0c;它提供了强大而灵活的工具&#xff0c;帮助开发者轻松地解析 HTML 和 XML 文档&#xff0c;并从中提取所需的数据。本文将深入探讨 Beautiful Soup 的使用方…

C#调用HttpClient.SendAsync报错:System.Net.Http.HttpRequestException: 发送请求时出错。

C#调用HttpClient.SendAsync报错&#xff1a;System.Net.Http.HttpRequestException: 发送请求时出错。 var response await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);问题出在SSL/TLS&#xff0c;Windows Server 2012不支持…

深入浅出MySQL事务实现底层原理

重要概念 事务的ACID 原子性&#xff08;Atomicity&#xff09;&#xff1a;即不可分割性&#xff0c;事务中的操作要么全不做&#xff0c;要么全做一致性&#xff08;Consistency&#xff09;&#xff1a;一个事务在执行前后&#xff0c;数据库都必须处于正确的状态&#xf…

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

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

文心智能体大赛:百度文心智能体平台初体验

写在前面 博文内容涉及&#xff1a;文心智能体大赛:文心智能体初体验理解不足小伙伴帮忙指正 &#x1f603;,生活加油 我徒然忘记了热闹&#xff0c;却来不及悟透真正的清冷(《四喜忧国》) 前言 徒然忘记了热闹&#xff0c;却来不及悟透真正的清冷(《四喜忧国》)&#xff0c;在…

【论文阅读|cryoET】ICE-TIDE

简介 三维cryoET重建的保真度进一步受到采集过程中物理扰动的影响。这些扰动以各种形式表现出来&#xff0c;例如连续采集之间的样本漂移&#xff0c;导致连续投影未对准&#xff0c;或者由于未散射的电子而导致二维投影中的局部变形。 传统的冷冻电子断层扫描工作流程需要对…

贪心题目总结

1. 最长递增子序列 我们来看一下我们的贪心策略体现在哪里&#xff1f;&#xff1f;&#xff1f; 我们来总结一下&#xff1a; 我们在考虑最长递增子序列的长度的时候&#xff0c;其实并不关心这个序列长什么样子,我们只是关心最后一个元素是谁。这样新来一个元素之后&#xf…

深入了解 Golang 多架构编译:交叉编译最佳实践

随着软件开发领域的不断发展&#xff0c;我们面临着越来越多的挑战&#xff0c;其中之一是如何在不同的平台和架构上部署我们的应用程序。Golang&#xff08;Go&#xff09;作为一种现代化的编程语言&#xff0c;具有出色的跨平台支持&#xff0c;通过其强大的多架构编译功能&a…

需求开发和管理

人们对需求术语的困惑甚至延伸到整个学科的称谓上。有些作者将整个范围都称为“需求工程”。有些人统称为“需求管理”。还有些人认为这些活动属于广义上的业务分析的一个分支。我们发现&#xff0c;最好将需求工程分为需求开发和需求管理&#xff0c;如图所示。不管项目遵循什…

C++笔记之Unix时间戳、UTC、TSN、系统时间戳、时区转换、local时间笔记

C++笔记之Unix时间戳、UTC、TSN、系统时间戳、时区转换、local时间笔记 ——2024-05-26 夜 code review! 参考博文 C++笔记之获取当前本地时间以及utc时间

网络统一监控运维管理解决方案(ppt原件方案)

网络统一监控运维管理解决方案 1. 构建完善的网络运维体系&#xff1a;通过组织、流程、制度的完善、支撑手段的建设&#xff0c;构建低成本高效率的IT运营体系&#xff0c;推动IT运营工作自动化、智能化、一体化化发展。 2. 构建网络一体化监控能力&#xff1a;构建从设备、…

QT之常用控件

一个图形化界面当然需要有各种各样的控件&#xff0c;QT也不例外&#xff0c;在QT designer中就有提供各种各样的控件&#xff0c;用以开发图形化界面。 而想使用好一个QT控件&#xff0c;就需要了解这些控件。 QWidget 在QT中&#xff0c;所有控件都继承自 QWidget 类&…