为什么 Windows 不像 Linux 可以混用 Debug 和 Release 库

比如在程序中链接使用了第三方的 Release 动态链接库,如果在 VS 中使用 Debug 模式编译,一般情况下都会报错,反之亦然

在 VS2010 之前,这种做法是可以编译通过的,但在运行阶段可能会出现奇怪的错误

从 VS2010 开始 Windows 增加了这种混用的检测,并在编译阶段进行报错

那么 Windows 这样做是为了避免什么问题呢?什么原因导致的问题呢?

这就提到运行时库(一般是指 C 运行时库:CRT),这是系统提供的基础库,Windows 一个有别于 Linux 的重要区别是:
在 Windows 下可以为一个进程加载多个 CRT,在 Linux 下则不支持这个行为

在 VS 中项目属性中可以查看和设置项目的 CRT,可以发现 exe/dll/lib 项目都可以设置自己要使用的 CRT,有多种 CRT 可以选择:

  • /MT Multi-threaded
  • /MTd Multi-threaded Debug
  • /MD Multi-threaded DLL
  • /MDd Multi-threaded DLL Debug

不带 Debug 的则表示 Release 版本的 CRT,不带 DLL 的表示使用静态库,这意味着甚至可以把 CRT 静态链接到目标中

默认情况下,程序在 Debug 模式下就会默认使用 Debug 模式的 CRT,除非手动修改了此选项,Release 模式下同理

那么这就很清楚了,如果第三方的 dll 库是 Release 版的,那么可以认为其使用的 CRT 是 Release 版的

而我们的程序 exe 项目如果在 Debug 模式下运行,则默认是使用 Debug 模式的 CRT,这就会导致 Windows 在同一个进程中同时加载了 Debug 和 Release 版的两个 CRT

而在 Windows 下 Debug 和 Release 的 CRT 是 ABI 不兼容的,甚至即便都是 Release/Debug 版本的 CRT,如果版本号不同也可能是不兼容的,而 ABI 不兼容的情况下,结构体、类、函数、变量在内存中的结构和状态可能是不同的,那么不同的 CRT 根据自己的实现处理同一块内存时就可能导致内存异常进而程序崩溃

举个便于理解的例子:

比如当 exe 调用 dll 中的函数,dll 使用自己的 CRT 在内存中为一个结构体创建了一个实例,即申请了一块内存存放结构体的数据,然后将这个结构体内存的指针返回给了 exe

在 exe 拿到此指针后,也会使用自己的 CRT 处理这个结构体指针指向的内存,比如:调用结构体的函数,甚至销毁了这个结构体(释放其内存)

这块内存在两个 ABI 不兼容的 CRT 看来,虽然头文件中此结构体的声明是相同的,但预期的内存结构和状态可能是不同的

比如根据 dll 的 CRT 实现,申请使用的内存大小为 10,而根据 exe 的 CRT 实现,这块内存大小应该是 20

那么如果 exe CRT 拿着这个指针去释放这块 dll CRT 申请的内存的话,就会把 20 个字节的内存空间释放掉,然后将它们分配给其他地方使用

这意味着后 10 个字节内存(原本不是 dll CRT 申请的),也被意外的释放了,那些内存原本可能是正在被使用的,进而就导致了内存一场,出现奇怪的错误

而 Linux 一直致力于保持运行时库的兼容性,且不允许在单个进程中加载多个运行时库,所以 Linux 混用 Debug 和 Release 一般没问题