为什么一些由旧编译器构建的库可以与现代代码链接,而另一些则不能



我们有很多预构建的库(主要通过CMake),使用Visual Studio 2017 v141构建。当我们试图在使用Visual STudio 2019 v142的项目中使用这些时,我们会看到以下错误:

错误C1047对象或库文件"boost_chrono-vc141-mt-gd-x32-1_68.lib"由另一个编译器的版本而不是其他对象。。。

另一方面,我们还使用了已有十多年历史的第三方供应商的预编译.lib,当与我们的代码库链接时,它们运行得很好。

是什么决定了一个图书馆是否需要重建,为什么一些古老的图书馆仍然可以使用,而其他只有一个版本的图书馆却不能使用?

ABI不兼容可能会导致一些问题。即使C++标准调用std::vectorstd::mutex之类的对象,并且它们需要具有特定的公共/受保护成员,如何生成这些类仍有待于实现。

在实践中,这意味着没有什么可以阻止GNU标准库将其数据字段按LLVM标准库之外的其他顺序排列,或者具有完全不同的私有成员。

因此,如果您试图通过向LLVM libc++构建的库发送GNU libstdc++向量来使用该库中的函数,则会导致UB。即使在同一个标准库上,不同的版本也可能会有所改变,这可能是一个问题。

为了避免这些问题,流行的C++库在其ABI中只使用C数据结构,因为(至少目前)每个编译器都为char*、int或struct生成相同的内存布局。

这些ABI问题可能出现在两个地方:

  • 当你使用动态库(.so和.dll文件)时,你的编译器可能不会说任何话,当你使用不兼容的C++对象调用库的函数时,你会得到未定义的行为
  • 当你使用静态库(.a和.lib文件)时,我真的不确定,我猜如果它发现有问题,它可能会打印一个错误,或者成功地编译了一个行为类似于上述的二进制文件的弗兰肯斯坦怪物

我将尝试回答一些积分部分,但请注意,这个答案可能是不完整的。有了来自同行的更多信息,我们也许能够构建出一个完整的答案!

simples类型的链接是指向C库的链接。由于没有类和重载函数名的概念,编译器创建者能够通过它们的纯名称创建函数的入口点。这似乎是相当准标准化的,因为我自己还没有遇到过一个纯粹的C库,至少不能链接到我的项目。您可以在C++代码中通过在函数声明前面加上extern "C"来选择这种行为。(这也使得从C#代码链接到库变得容易)以下是关于extern "C"的详细解释。但据我所知,这种行为并不规范;这很简单,似乎只有一个合理的解决方案。

进入C++,我们开始遇到函数、变量和结构名称重复的情况。让我们在这里讨论一下重载函数。为此,编译器创建者必须在void a(); void a(int x); void a(char x); ...和它们各自的库表示之间建立某种映射。由于这个过程也不是标准化的(参见本线程),并且这个过程比C的1对1映射复杂得多,因此不同编译器甚至编译器版本的ABI可能会以任何方式不同。

现在,给定两个具有不同名称篡改方案的编译器(或链接器,我找不到一个资源来指定哪一个确切负责篡改,但由于这个过程没有标准化,它也可以外包给cthulhu),创建以下函数入口点(简化):

compiler1
_a_
_a_int_
_a_char_
compiler2
_a_NULL_
_a_++INT++_
_a_++CHAR++_

不同的链接者不会理解您特定流程的输出;CCD_ 12将尝试在仅包含CCD_ 14的库中搜索CCD_。由于链接器不能使用模糊字符串比较(这可能会导致启示录imho),因此它不会在库中找到您的函数。也不要被这个例子的简单性所欺骗:对于命名空间、类、方法等每个功能,都必须实现一个方法来将函数名映射到入口点或内存结构。

举个例子,你很幸运,你使用了同一个发行商的库,该发行商编码了一些逻辑来检测旧库。通常,您会收到类似<something> could not be resolved或其他一些复杂、令人恼火和/或毫无帮助的错误消息。

关于Visual Studio和库的一些信息和经验转储:

  • 通常,Visual C++套件不支持不同版本之间的交联库,但你可能很幸运,它可以工作。不要依赖它
  • 自VC++2015以来,微软保证库的ABI是兼容的,正如drescherjm评论的那样:链接到微软文档
  • 一般来说,当使用来自不同套件的库时,您应该始终保持谨慎,因为n.1.8e9-where’s-my-share m在这里(这里是您的共享btw)评论了与其他库和运行时的依赖关系。一般来说,不能控制图书馆的建设是一件大事

除了Tzigs的答案外,编辑寻址内存布局不兼容:不同的名称篡改方案似乎部分是有意保护用户免受不兼容库的链接。这个答案详细介绍了它

G++不像其他C++编译器那样进行名称篡改。这意味着用一个编译器编译的对象文件不能与另一个编译器一起使用。

这种效果是有意的[…]。

错误C1047

这是由/GL全局优化或/LTGC链路时间代码生成引起的

这些使用.obj中的信息来执行全局优化。如果存在,VS会查看生成原始.lib的编译器,如果它们不同,就会发出错误。这些编译开关用于来自单个编译器的代码,不用于跨版本使用。

其他可以工作的构建没有开关,因此是兼容的。

Visual studio已开始使用新的#pragmadetect_mismatch

这会导致旧版本通过检测版本更改来识别它与新版本不兼容。

非常旧的构建没有/支持pragma,因此没有检查。

当您构建一个lib时,它的依赖项会被链接器加载并满足,但这并不能保证工作。一个定义规则向开发人员签署了一份合同,即在编译的二进制文件中,相同命名函数的所有实现都是相同的。如果这来自不同的编译器,那可能不是真的,因此链接器可以选择任何一个,从而导致潜在的错误,即新旧代码的混合物被链接到二进制文件中。

如果std::string的定义或实现发生了更改,它可能会链接,但代码存在缺陷。

这个新的编译器检查会导致早期失败,对此我深表赞同。

最新更新