调试和发布配置 (c++) 中构建的共享库在用法上有什么区别?

What's the difference in usage between shared libraries built in debug and release configuration (c++)?

我在使用共享库(Win 10 中的 .dll)时遇到问题。

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

工作流程是:

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

步骤 B. 构建 xlibtest 和 link 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
}

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

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

我在我的项目中使用了英特尔 MKL 库,并且 link 在调试和发布配置中使用 相同的 库。它似乎工作正常。

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

P.S。我不使用静态库 link 的原因是编译时有错误并且需要相同的配置(即 debug/release)。

--更新--

我正在使用:

CMake 3.5.2;

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

IDE:VS 2015 社区;

OS: Windows 10, 64 位。

CPU:英特尔 i7 4930K

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

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

在 Debug/Release 中编译 - 对代码的影响

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

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

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

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

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

如果您有两个模块

,问题将会发生
  1. 使用不同版本或类型的二进制表示
  2. 该类型的交换 objects

问题主要发生在动态 linking 中(但也可能发生在静态 linking 中,例如,pre-built 静态库与其 headers).

如果针对定义该类型的库的二进制不兼容版本编译模块(使用有问题的类型)和 linked(使用处理有问题类型的库代码),就会发生这种情况。

因此,特别是对于 C++ 标准库,要避免 and/or 修复这些问题,您需要

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

在 link 时间区分不兼容的库版本

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

在 link 时间(静态或动态),没有 "build configurations" 这样的东西。所有这些都是许多模块。所有 linker 看到的都是对其他模块的引用(仅限动态 linking)和 exported/imported 条目。它所做的就是找到引用的模块(同样,仅动态;在静态中,模块通常是明确指定的),然后将导出和导入的条目匹配在一起。 C++ export/import 条目名称包含编码 ("mangled") 形式的条目的完整签名(具体来说,不兼容的编译器必须使用不同的修改方案,因此它们生成的条目不会冲突。

linker 区分库的两个版本的唯一方法是:

  1. 为其提供不同的搜索路径/明确指定不同的模块
  2. 使用不同的模块名称来link反对
  3. 使用不同的条目名称(意味着不同类型 names/function 签名)

3) 通常不实用(需要大量样板代码)并且在动态 linking 中,没有 2) 就无法使用,因为 linker 首先匹配模块名称。 1)(搜索路径风格)不缩放。

总结:

  • 2)是system-wide动态linking(_d后缀,版本后缀)
  • 的标准做法
  • 1) 是构建环境(静态和动态)的标准做法
  • 1)(搜索路径风格)用于 link 针对库的私有副本(正如所说,这不会扩展)