引子

前两天写代码的时候遇到一个奇怪的bug。

背景是这样:我编译dll工程A,A要链接一个静态库B,两者都是我自己编译的。结果链接的时候就提示:

1
error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MTd_StaticDebug' doesn't match value 'MDd_DynamicDebug' in XXX

常见的链接错误,发生在lib和lib使用者的RuntimeLibrary设置不一致的情况下。

然而我这是用CMake生成的工程,编译选项都是用一个统一的函数设置的啊,仔细看了看生成的工程中的配置项,没错啊,两边都是/MTd。

再用16进制查看lib文件,里面确实找到了相关字样:

1
/FAILIFMISMATCH:"RuntimeLibrary=MDd_DynamicDebug"

是正常情况下使用/MDd的时候才会产生的。

那我一个好好的/MTd,怎么就生出了个/MDd呢?

最后经过多番测试,发现是我自定义的一个宏在作怪,当我写这样的代码的时候:

1
2
#define _DLL //注意这个名字
#include <string>

对这个cpp文件,不管你工程怎么设置,传给cl的参数怎么变换,最后生成的obj一定是MD的。

进一步的,通过在文件的编译命令中加入一个/P(输出预处理结果),也找到了在<string>头文件(或其包含的某个文件内)中的相关命令:

1
#pragma detect_mismatch("RuntimeLibrary", "MDd_DynamicDebug")

找到了根源,把自己定义的这个_DLL宏改个名字,问题也就解决了。不过这是怎么个原理呢?为什么一个宏定义居然可以让标准库的头文件去控制代码生成时的行为呢?

编译器预定义宏

原来每种编译器都会有自己的预定义宏。其中包括C和C++语言标准所制定的宏:__cpluscplus__FILE____LINE__ 等。也包括各个版本编译器自己定义的宏。

比如对于微软的MSVC编译器,其所支持的所有预定义宏可以在这里查看。

而其他大部分编译器,其所支持的预定义宏可以通过命令自己看到。比如g++的用法:

1
g++ -dM -E -x c++ /dev/null

而具体到本文所遇到的问题,是VC++的这么一个宏:

  • _DLL Defined as 1 when the /MD or /MDd (Multithreaded DLL) compiler option is set. Otherwise, undefined.

VC++所带的标准库也会根据这个宏来进行一些不同的操作,从而导致了上述问题。