在调试和发布配置(c ++)中构建的共享库之间的使用差异是什么?

时间:2016-05-31 08:59:23

标签: c++ dll binary-compatibility

我使用共享库时出现问题(Win 10中为.dll)。

我在两个不同的配置中构建一个名为xlib的lib,并尝试在名为xlibtest的项目中通过CMake测试它们。

工作流程是:

步骤A.构建xlib => xlib.dll + xlib.lib(仅符号)+ xlib.pdb [可选]

步骤B.构建xlibtest并链接xlib.lib => xlibtest.exe

如果在步骤A和步骤B中构建配置不同,那么当我运行xlibtest.exe时,会出现问题。 例如,我有一个功能

//xlib.h    
void foo(std::string input); 

在xlib中,当我调用此函数时它会崩溃。

//xlibtest.cpp
int main() {
    xlib::foo("test"); // in debug mode I found the string pass to foo is wrong
}

我已经搜索并找到了有关此问题的一些解释。这可能是因为调试和发布配置的内存模式不同。并且建议不要混用这两种配置,即在调试配置中使用debug lib并在发布配置中释放lib。

然后是问题所在。我可以构建自己的两个版本的库。但是我可以为那些外部库做些什么呢?

我在项目中使用了英特尔MKL库,并在调试和发布配置中链接了相同的库。看起来效果很好。

但是当我使用Boost库时,调试版和发布版之间可能存在差异,因为我们可以在安装Boost库时决定我们构建的版本。

P.S。我之所以不与静态库链接的原因是编译时存在错误,需要相同的配置(即调试/发布)。

- 更新 -

我正在使用:

CMake 3.5.2;

编译器:用于Intel 64 VS2015环境的Intel icl 16.0 update 3;

IDE:VS 2015社区;

操作系统:Windows 10,64位。

CPU:Intel i7 4930K

很抱歉没有提供有关工具链的这些信息。我以前认为这是一个普遍的问题,与我使用的工具链无关。 @Hans Passant

我认为来自@John D的回答可能会让我感到困惑。在我了解到混合调试和发布配置存在问题后,我无法理解为什么使用英特尔MKL库没有区别,这就是我发布此问题的原因。这是因为英特尔MKL lib是一个没有std :: string的C lib吗?

1 个答案:

答案 0 :(得分:0)

在调试/发布中编译 - 对代码的影响

从技术上讲,库的“Debug”和“Release”版本是使用不同的编译器开关和预处理器宏构建的。它与编译具有可选功能的库没有根本的区别。

  • 编译器开关通常不会影响代码在高级别的工作方式。在低级别上存在轻微差异,其中相关规范未指定行为(例如,独立操作的顺序,存储器映射,变量的物理位置,编译器生成的检测代码)但是如果您依赖于这些,那么您正在做错了。
  • 另一方面,
  • 预处理器宏可以以任何方式改变代码行为通常,DEBUG宏启用具有各种运行时检查和诊断的代码块。后者可能包括向数据类型添加成员。

因此,您构建的C ++代码的调试和发布版本在一般情况下是二进制不兼容的。

这代表C ++标准库的代码。虽然它通常不是每次都从stratch编译,而是插入少数预编译版本之一(包括委托给a的“thunk”版本) DLL)。

真实的部分:陷阱和解决方案

如果您有两个模块

会出现问题
  1. 使用类型
  2. 的不同版本或二进制表示
  3. 交换该类型的对象
  4. 问题主要发生在动态链接中(但是如果预先构建的静态库与其标题不对应,也可能在静态链接中发生)。

    如果模块已编译(使用有问题的类型)并链接(使用有问题类型的库代码)对定义该类型的库的二进制不兼容版本,则会发生这种情况。

    因此,特别是对于C ++标准库,要避免和/或修复它们,您需要

    • 确保所有交换C ++类型的模块都已编译并链接到二进制兼容(对于要交换的类型)C ++运行时版本
      • 这是相对容易检查是否所有这些模块都动态链接到它,但如果有些模块是静态编译的话,那就不那么多了
      • 基本上,在理想情况下,所有模块甚至应该使用运行时的相同实例(在Linux中,它包括libgcc) - 例如可能需要在它们之间传递例外。如果您的环境如此异构以至于无法实现,那么完全放弃C ++类型作为交换格式可能是明智的。

    在链接时区分不兼容的库版本

    (指定正确的模块并命名自己的模块,以便区分它们)

    在链接时(静态或动态),没有“构建配置”这样的东西。所有这些都有很多模块。所有链接器都看到对其他模块(仅动态链接)和导出/导入条目的引用。它所做的就是找到引用的模块(再次,仅动态;在静态中,通常明确指定模块),然后将导出和导入的条目匹配在一起。 C ++导出/导入条目名称包含编码(“损坏”)形式的条目的整个签名(具体而言,不兼容的编译器必须使用不同的修改方案,因此它们生成的条目不会发生冲突。

    链接器区分两个版本库的唯一方法是:

    1. 为其提供不同的搜索路径/明确指定不同的模块
    2. 使用不同的模块名称链接
    3. 使用不同的条目名称(暗示不同的类型名称/功能签名)
    4. 3)通常不实用(需要大量样板代码),并且在动态链接中,如果没有2),则无法使用,因为链接器首先匹配模块名称。 1)(搜索路径风味)不会缩放。

      总结:

      • 2)是系统范围动态链接的标准做法(_d后缀,版本后缀)
      • 1)是构建环境(静态和动态)的标准实践
      • 1)(搜索路径风味)用于链接库的私有副本(如上所述,这不会扩展)