C++ 工程实践(4):二进制兼容性

UE4模块版本冲突报错:诊断、编译与版本管理全攻略 在虚幻引擎4(UE4)开发中,模块(Module)是构成项目功能的核心单元,其本质是编译后的动态链接库(DLL)或静态库,与引擎版本深度绑定。模块化架构的原理在于分离引擎核心功能与游戏逻辑,提升代码复用与团队协作效率。当模块的二进制文件丢失或版本标识符与当前引擎不匹配时,便会触发“模块丢失或版本不匹配”的经典报错,其技术价值在于强制开发者关注项目资产与构建环境的版本一致性,这是保障大型项目长期可维护性的关键。该问题常见于从版本控制系统拉取代码、团队协作或引擎升级等应用场景。本文聚焦于UE4项目开发中由引擎版 阅读详情

陈硕 (giantchen_AT_gmail)

Blog.youkuaiyun.com/Solstice

本文主要讨论 Linux x86/x86-64 平台,偶尔会举 Windows 作为反面教材。

C/C++ 的二进制兼容性 (binary compatibility) 有多重含义,本文主要在“头文件和库文件分别升级,可执行文件是否受影响”这个意义下讨论,我称之为 library (主要是 shared library,即动态链接库)的 ABI (application binary interface)。至于编译器与操作系统的 ABI 留给下一篇谈 C++ 标准与实践的文章。

什么是二进制兼容性

在解释这个定义之前,先看看 Unix/C 语言的一个历史问题:open() 的 flags 参数的取值。open(2) 函数的原型是

int open(const char *pathname, int flags);

其中 flags 的取值有三个: O_RDONLY,  O_WRONLY,  O_RDWR。

与一般人的直觉相反,这几个值不是按位或 (bitwise-OR) 的关系,即 O_RDONLY | O_WRONLY != O_RDWR。如果你想以读写方式打开文件,必须用 O_RDWR,而不能用 (O_RDONLY | O_WRONLY)。为什么?因为 O_RDONLY, O_WRONLY, O_RDWR 的值分别是 0, 1, 2。它们不满足按位或 。

那么为什么 C 语言从诞生到现在一直没有纠正这个不足之处?比方说把 O_RDONLY, O_WRONLY, O_RDWR 分别定义为 1, 2, 3,这样 O_RDONLY | O_WRONLY == O_RDWR,符合直觉。而且这三个值都是宏定义,也不需要修改现有的源代码,只需要改改系统的头文件就行了。

因为这么做会破坏二进制兼容性。对于已经编译好的可执行文件,它调用 open(2) 的参数是写死的,更改头文件并不能影响已经编译好的可执行文件。比方说这个可执行文件会调用 open(path, 1) 来写 文件,而在新规定中,这表示读 文件,程序就错乱了。

以上这个例子说明,如果以 shared library 方式提供函数库,那么头文件和库文件不能轻易修改,否则容易破坏已有的二进制可执行文件,或者其他用到这个 shared library 的 library。操作系统的 system call 可以看成 Kernel 与 User space 的 interface,kernel 在这个意义下也可以当成 shared library,你可以把内核从 2.6.30 升级到 2.6.35,而不需要重新编译所有用户态的程序。

所谓“二进制兼容性”指的就是在升级(也可能是 bug fix)库文件的时候,不必重新编译使用这个库的可执行文件或使用这个库的其他库文件,程序的功能不被破坏。

见 QT FAQ 的有关条款:http://developer.qt.nokia.com/faq/answer/you_frequently_say_that_you_cannot_add_this_or_that_feature_because_it_woul

在 Windows 下有恶名叫 DLL Hell,比如 MFC 有一堆 DLL,mfc40.dll, mfc42.dll, mfc71.dll, mfc80.dll, mfc90.dll,这是动态链接库的本质问题,怪不到 MFC 头上。

有哪些情况会破坏库的 ABI

到底如何判断一个改动是不是二进制兼容呢?这跟 C++ 的实现方式直接相关,虽然 C++ 标准没有规定 C++ 的 ABI,但是几乎所有主流平台都有明文或事实上的 ABI 标准。比方说 ARM 有 EABI,Intel Itanium 有 http://www.codesourcery.com/public/cxx-abi/abi.html ,x86-64 有仿 Itanium 的 ABI,SPARC 和 MIPS 也都有明文规定的 ABI,等等。x86 是个例外,它只有事实上的 ABI,比如 Windows 就是 Visual C++,Linux 是 G++(G++ 的 ABI 还有多个版本,目前最新的是 G++ 3.4 的版本),Intel 的 C++ 编译器也得按照 Visual C++ 或 G++ 的 ABI 来生成代码,否则就不能与系统其它部件兼容。

C++ ABI 的主要内容:

  • 函数参数传递的方式,比如 x86-64 用寄存器来传函数的前 4 个整数参数
  • 虚函数的调用方式,通常是 vptr/vtbl 然后用 vtbl[offset] 来调用
  • struct 和 class 的内存布局,通过偏移量来访问数据成员
  • name mangling
  • RTTI 和异常处理的实现(以下本文不考虑异常处理)

C/C++ 通过头文件暴露出动态库的使用方法,这个“使用方法”主要是给编译器看的,编译器会据此生成二进制代码,然后在运行的时候通过装载器(loader)把可执行文件和动态库绑到一起。如何判断一个改动是不是二进制兼容,主要就是看头文件暴露的这份“使用说明”能否与新版本的动态库的实际使用方法兼容。因为新的库必然有新的头文件,但是现有的二进制可执行文件还是按旧的头文件来调用动态库。

这里举一些源代码兼容但是二进制代码不兼容例子

  • 给函数增加默认参数,现有的可执行文件无法传这个额外的参数。
  • 增加虚函数,会造成 vtbl 里的排列变化。(不要考虑“只在末尾增加”这种取巧行为,因为你的 class 可能已被继承。)
  • 增加默认模板类型参数,比方说 Foo 改为 Foo >,这会改变 name mangling
  • 改变 enum 的值,把 enum Color { Red = 3 }; 改为 Red = 4。这会造成错位。当然,由于 enum 自动排列取值,添加 enum 项也是不安全的,除非是在末尾添加。

给 class Bar 增加数据成员,造成 sizeof(Bar) 变大,以及内部数据成员的 offset 变化,这是不是安全的?通常不是安全的,但也有例外。

  • 如果客户代码里有 new Bar,那么肯定不安全,因为 new 的字节数不够装下新 Bar。相反,如果 library 通过 factory 返回 Bar* (并通过 factory 来销毁对象)或者直接返回 shared_ptr,客户端不需要用到 sizeof(Bar),那么可能是安全的。 同样的道理,直接定义 Bar bar; 对象(无论是函数局部对象还是作为其他 class 的成员)也有二进制兼容问题。
  • 如果客户代码里有 Bar* pBar; pBar->memberA = xx;,那么肯定不安全,因为 memberA 的新 Bar 的偏移可能会变。相反,如果只通过成员函数来访问对象的数据成员,客户端不需要用到 data member 的 offsets,那么可能是安全的。
  • 如果客户调用 pBar->setMemberA(xx); 而 Bar::setMemberA() 是个 inline function,那么肯定不安全,因为偏移量已经被 inline 到客户的二进制代码里了。如果 setMemberA() 是 outline function,其实现位于 shared library 中,会随着 Bar 的更新而更新,那么可能是安全的。

那么只使用 header-only 的库文件是不是安全呢?不一定。如果你的程序用了 boost 1.36.0,而你依赖的某个 library 在编译的时候用的是 1.33.1,那么你的程序和这个 library 就不能正常工作。因为 1.36.0 和 1.33.1 的 boost::function 的模板参数类型的个数不一样,其中一个多了 allocator。

这里有一份黑名单,列在这里的肯定是二级制不兼容,没有列出的也可能二进制不兼容,见 KDE 的文档:http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B

 

哪些做法多半是安全的

前面我说“不能轻易修改”,暗示有些改动多半是安全的,这里有一份白名单,欢迎添加更多内容。

只要库改动不影响现有的可执行文件的二进制代码的正确性,那么就是安全的,我们可以先部署新的库,让现有的二进制程序受益。

  • 增加新的 class
  • 增加 non-virtual 成员函数
  • 修改数据成员的名称,因为生产的二进制代码是按偏移量来访问的,当然,这会造成源码级的不兼容。
  • 还有很多,不一一列举了。

欢迎补充

反面教材:COM

在 C++ 中以虚函数作为接口基本上就跟二进制兼容性说拜拜了。具体地说,以只包含虚函数的 class (称为 interface class)作为程序库的接口,这样的接口是僵硬的,一旦发布,无法修改。

比方说 M$ 的 COM,其 DirectX 和 MSXML 都以 COM 组件方式发布,我们来看看它的带版本接口 (versioned interfaces):

  • IDirect3D7, IDirect3D8, IDirect3D9, ID3D10*, ID3D11*
  • IXMLDOMDocument, IXMLDOMDocument2, IXMLDOMDocument3

话句话说,每次发布新版本都引入新的 interface class,而不是在现有的 interface 上做扩充。这样不能兼容现有的代码,强迫客户端代码也要改写。

回过头来看看 C 语言,C/Posix 这些年逐渐加入了很多新函数,同时,现有的代码不用修改也能运行得很好。如果要用这些新函数,直接用就行了,也基本不会修改已有的代码。相反,COM 里边要想用 IXMLDOMDocument3 的功能,就得把现有的代码从 IXMLDOMDocument 全部升级到 IXMLDOMDocument3,很讽刺吧。

tip:如果遇到鼓吹在 C++ 里使用面向接口编程的人,可以拿二进制兼容性考考他。

解决办法

采用静态链接

这个是王道。在分布式系统这,采用静态链接也带来部署上的好处,只要把可执行文件放到机器上就行运行,不用考虑它依赖的 libraries。目前 muduo 就是采用静态链接。

通过动态库的版本管理来控制兼容性

这需要非常小心检查每次改动的二进制兼容性并做好发布计划,比如 1.0.x 系列做到二进制兼容,1.1.x 系列做到二进制兼容,而 1.0.x 和 1.1.x 二进制不兼容。《程序员的自我修养》里边讲过 .so 文件的命名与二进制兼容性相关的话题,值得一读。

用 pimpl 技法,编译器防火墙

在头文件中只暴露 non-virtual 接口,并且 class 的大小固定为 sizeof(Impl*),这样可以随意更新库文件而不影响可执行文件。当然,这么做有多了一道间接性,可能有一定的性能损失。见 Exceptional C++ 有关条款和 C++ Coding Standards 101.

Java 是如何应对的

Java 实际上把 C/C++ 的 linking 这一步骤推迟到 class loading 的时候来做。就不存在“不能增加虚函数”,“不能修改 data member” 等问题。在 Java 里边用面向 interface 编程远比 C++ 更通用和自然,也没有上面提到的“僵硬的接口”问题。

(待续)

C++ 工程实践(5):避免使用虚函数作为库的接口 摘要:作为 C++ 动态库的作者,应当避免使用虚函数作为库的接口。这么做会给保持二进制兼容性带来很大麻烦,不得不增加很多不必要的 interfaces,最终重蹈 COM 的覆辙。 本文主要讨论 Linux x86 平台,会继续举 Windows/COM 作为反面教材。 本文是上一篇《C++ 工程实践(4):二进制兼容性》的延续,在写这篇文章的时候,我原本以外大家都对“以虚函数作为接口”的害处达成共识,我就写得比较简略,看来情况不是这样,我还得展开谈一谈。 阅读详情

相关推荐

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

假设你的应用程序引用的一个库某天更新了,虽然 API 和调用方式基本没变,但你需要重新编译你的应用程序才能使用这个库,那么一般说这个库是源码兼容(Source compatible);反之,如果不需要重新编译应用程序就能使用新版本的库,那么说这个库跟它之前的版本是二进制兼容的(Binary compatible)。对于 C++ 平台的应用商店程序,怎样保证平台商店版本更新了,商店里面的应用程序能在不更新的情况下继续使用,就变成了一件十分重要的事情。

bigchen 3061

C++二进制兼容性总结

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、什么是二进制兼容?二、保持二进制兼容的条件1.可以做的事情2.不可以做的事情补充说明应该做的总结 前言 对于C-Like等编译型语言,没有虚拟机的协助,随着项目维护,必然会产生很多兼容性问题。此文作为个人对其中的二进制兼容性进行一下总结。 一、什么是二进制兼容? 对于一个库,如果使用旧版本的主程序,无需重新编译就可以连接新版本的库,并正常运行,则这个库是二进制兼容的。 如果主程序不需要修改,但需要重新编译才可以使用新版

工作学习记录 2994

红外与可见光融合避坑指南:LLVIP数据集实测6大算法效果对比

本文基于专为弱光视觉设计的LLVIP数据集,对GTF、DenseFuse、FusionGAN、IFCNN等6种主流红外与可见光图像融合算法进行了深度实测与对比。通过客观指标与可视化分析,揭示了算法在极端弱光下易出现特征丢失的共性问题,并探讨了融合图像对下游目标检测任务的实际增益,为工程选型与算法调优提供了实用指南。

weixin_29092031的博客 439

c++二进制兼容及解决方法

什么是二进制兼容 二进制兼容ABI(application binary interface)主要指动态库文件单独升级,现有用到老动态库的应用程序是否受到影响。 1.升级库文件,不影响使用库文件的程序。 2.新库必然有新头文件,但是旧的二进制可执行文件还是按照旧的头文件中的“使用说明”来调用库。 意思就是你应用程序A调用库B1.0,现在库B升级了,变成B1.1,应用程序A调用库B1.1还是...

芒果黑的博客 3616

TMS320F28388D 学习笔记2 高精度PWM (HRPWM)

HRPWM的学习记录。

whyorwhnt的专栏 9534

Plaxis软件简介及 Plaxis Python API环境搭建、自动化建模、Python全自动实现、高级案例分析

本次课程主要针对岩土工程中的各种问题(塑性、渗流、固结、动力、稳定安全、热力TM),用四天时间,教大家如何一步一步地搭建自己的Plaxis模型,包括Plaxis Python API的连接与配置、外部Python编译器的使用、Python命令流自动建模过程、Python远程脚本读取输出结果等,其中的Python建模模块尽可能做到让学员零基础开始,通过提供标准命令流库,使用搭积木、摆乐高的形式模块化进行,加强学员的易上手、可操作性。(2)钻孔的建立、土层的建立、土层属性、水力条件及初始条件、导入土层。

CCfz566的博客 1463

二进制兼容

二进制兼容(Binary Compatibility)是一个软件工程中的概念,它指的是在软件组件(如库、框架或操作系统服务)的更新过程中,新版本的组件能够与旧版本保持一定程度的互换性,即不需要重新编译那些依赖于该组件的应用程序或其它库,就能保证这些程序或库正常运行并发挥预期功能。举个例子:提供了一个dll的exe使用,突然改变了一下这个dll,这时如果exe不重新编译,使用新的dll也不会出问题,那么就可以说这个dll是二进制兼容的。

liunanya的博客 1400

XYC Q8开源掌机刷第三方系统教程

分区结束后直接将各种游戏文件放入OD分区的Roms文件夹下

m0_60352504的博客 4569

C++二进制兼容性问题

一开始是在Qt的D-Pointer中知道二进制兼容性这个概念。 二进制兼容性是什么? 当动态库更新的时候,如果使用该动态库的应用程序不需要重新编译,我们就说这是二进制兼容的。例如我们可以更换Qt动态库而不需要重新编译应用程序,应用程序可以继续运行。 为什么要保证二进制兼容性? 因为违反二进制兼容的行为容易出错,UB,设置崩溃。 什么行为会破坏二进制兼容?什么行为不会? 破坏二进制兼容性...

回头太难 1112

Arduino与Proteus仿真实例-LCD2004-I2C(PCF8574)驱动仿真

LCD2004-I2C(PCF8574)驱动仿真 PCF8574/74A 通过两线双向 I2C 总线(串行时钟 (SCL)、串行数据 (SDA))提供通用远程 I/O 扩展。 LCD2004,即2004字符型液晶显示器,是一种点阵模块,用于显示字母、数字、字符等。 它由 5x8 个点阵位置组成; 每个位置可以显示一个字符。 两个字符之间有一个点间距,行之间有一个空格,从而将字符和行分开。 模型 2004 表示它显示 4 行,每行 20 个字符。 在前面的实例中对PCF8574和LCD2004的已经做了详细介

视觉与物联智能 1165

Java SSH框架学习

Java SSH框架学习

私信可回答各种问题~ 1789

C++ 工程实践(4):二进制兼容性http://blog.youkuaiyun.com/Solstice/article/details/6233478

C++ 工程实践(4):二进制兼容性 标签: c++libraryinterfacemfcclass编译器 2011-03-09 10:46 17338人阅读 评论(61) 收藏 举报  分类:   C++ 工程实践(16)  c++(53)  版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+] 陈

诺尔曼 1098

Python反爬实战|动态渲染+高可用IP代理集群,稳定爬取京东/淘宝10万+商品数据

做Python电商爬虫的同学,应该都对京东、淘宝的反爬机制深有体会——这两个平台的反爬体系,堪称国内网站的天花板,远比普通资讯、政务网站的反爬严格十倍不止。用requests库请求商品列表,返回的永远是只有骨架的空HTML,商品标题、价格、销量全是前端JS异步加载,抓包分析接口发现参数全是加密的signtoken,解密成本无限高;用selenium开无头浏览器爬取,刚爬3-5页就触发风控,页面直接返回403,IP被封禁,甚至连Cookie都直接失效;

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1585

Visual Stuido笔记:C++二进制兼容性之间的兼容性

Visual Studio 2013和更早的Microsoft C++(MSVC)编译器工具集不能保证主要版本的二进制兼容性。您无法将这些工具集的不同版本构建的对象文件,静态库,动态库和可执行文件链接。ABI,对象格式和运行时库是不兼容的。我们在Visual Studio 2015及以后的版本中改变了这种行为。由任何这些版本的编译器编译的运行时库和应用程序都是兼容的。它反映在C++工具集的主要数字中,自Visual Studio 2015以来的所有版本开始为14个。

憧憬,思考,奋斗,飞翔 1270

查找【优快云】- 人工客服

【代码】查找【优快云】- 人工客服。

👨‍💻 前端开发者 | 技术分享者 1403

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

理解二进制ABI兼容性

ABI是 Application Binary Interface 的缩写,当我们以二进制形式(非源码形式)发布我们的动态库时,就需要关心ABI兼容(也称二进制兼容)。 对于静态库,更新静态库始终都需要该库的使用方重新编译,因此不存在ABI兼容的说法。

while(1) { smile(); } 4457

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Visual Studio 版本之间的 C++ 二进制兼容性(ABI)

由这些编译器版本中的任何一个编译的运行时库和应用程序都是二进制兼容的。我们确保最新版本的可再发行组件包具有所有最新的功能、安全更新和错误修复。如果您的 Windows XP 应用程序部署了或更新到更高版本的可再发行组件包,这些应用程序将无法运行。在某些情况下,由较新版本的工具集构建的隐式链接 DLL 的导入库可以使用较早版本的工具集进行链接——特别是如果导入库严格使用。当您混合使用不同受支持版本的工具集构建的二进制文件时,可再发行组件包的版本必须至少与任何应用程序组件使用的最新工具集一样新。

抟土成人祖,炼石补青天。眼中览银河,手可摘星辰。 1690

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

PPPwn_cpp终极指南:如何通过C++重写实现PlayStation 4远程代码执行

PPPwn_cpp是一个基于C++重写的PlayStation 4 PPPoE远程代码执行漏洞利用工具,它为安全研究人员和游戏机爱好者提供了一个强大而高效的安全研究平台。通过优化性能和跨平台支持,这个项目将原始的Python实现转化为更快速、更稳定的C++版本,让PS4安全研究变得更加简单和可靠。无论你是想要探索游戏机安全漏洞的开发者,还是对系统安全感兴趣的研究者,PPPwn_cpp都能为你提供完

gitblog_00022的博客 997

Visual Studio 各版本之间的 C++ 二进制兼容性

Visual Studio 各版本二进制兼容性

weixin_41885527的博客 2454

静态链接中的那点事儿(2):C++二进制兼容性及跨平台初步

C++的一些语言特性使之必须由编译器和链接器共同支持才能完成工作。最主要的有两个方面,其一,C++的重复代码的消除;其二,全局构造与析构。此外,由于C++的各种特性,比如虚函数、函数重载、继承、异常等,使得C++背后的数据结构异常复杂。而且最为不幸的是,这些数据结构往往在不同的编译器和链接器之间不能相互通用,使得C++程序的二进制兼容性成为一个难题。本篇博客将结合项目经验初步讨论C++程序的二进制

沈春旭的博客 2094
上一篇: C++ 工程实践(3):采用有利于版本管理的代码格式
下一篇: C++ 工程实践(5):避免使用虚函数作为库的接口
陈硕
博客等级 码龄26年 1万+粉丝 144原创
评论 63
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值