要说这个问题得先说VC++中的编译方式,参考:传统VC运行时库以及libcmt,msvcrt等内容.mht、VC140之后的VC++运行时.mht
lib静态库是一种在Windows上打包各个obj(已编译的目标二进制代码)的一个集合体格式文件,也就是说只要给定所需的obj文件它也能像类似zip那样简单打包出lib文件
——这里白易安装目录\tools内有该软件的实现,大家可以亲自动手试一试(或者把需求扔给AI让它也帮你做一个)
首先x86和x64这个很好理解(不同的CPU架构编译的机器码不同嘛),那现在要说的就已经砍半了
普通的Debug版编译使用的命令行是/MDd(因为现在都是多线程版的运行时库所以M字不用看了),大D表示的是DLL版,小d表示的是debug版,
而无大D选项的则是代表依赖C/C++的静态库,而后补上一个大T表示的是Thread线程(即现在的C运行时库中提供的命令基本都跨线程安全,VC6 时代的 /ML 已经没了),那你现在这里依赖DLL动态库那种本身也应该写/MTDd才比较好吧,故实际上VC编译器的命名并不好~有一定迷惑性。。
为了摆脱这种迷惑性,白易模块在命名的时候规范了但凡是静态库的【引用CRT静态版】都以"_static.lib"作为后缀命名
而后每种静态库的调试版和发布版都以Debug和Release文件夹作为区分,而编译64位的版本则再以x64文件夹作为独立目录首内再包含Debug和Release,无x64目录首的Debug和Release均为x86版本
静态库虽然自身是用户那边实现的C/C++代码编译到二进制的库的集合(注:仅以Debug形式编译或Release版本编译它的用户代码有很大的差异,static和非static的lib库从实现上无太大差别);
再来,一般来说用户代码或多或少都会依赖底层C/C++的库代码,那么这里就会有两种形式的依赖(是依赖静态版的C/C++库还是动态版的C/C++库),
但无论哪种形式的依赖(static或非static)其实在用户编译的lib文件中都不会包含C/C++库中代码的具体实现,而仅仅只是对依赖进行简单的声明(并不会说我static编译方式编译了这个lib静态库里边就包含了包括所有C/C++库的底层二进制代码),也就是说即便我们的静态库以/MDd编译后理论上后期可以经过一些简单手术也能让其改为/MTd
对于编译出的用户lib文件,只有在主项目是具体的[控制台、Win32窗口、Win32动态库]等实际工程项目时才会参与链接,才能进一步真正依据用户lib文件中所声明的依赖确立要引入的C/C++运行时库
当然主项目自己也会声明依赖的是何种运行时库,
但一般来说项目整体要统一一种形式的运行时库(主项目声明依赖的动态库形式,那么所有的lib编译的依赖就必须都是动态库),否则
这部分的详细论述我放到最后:#如果lib编译的依赖VC静态库是VS2017而主项目VS2019是依赖动态库会怎么样
因为这是两套完全不同的代码体系包括所依赖的VC运行时库,虽然微软官方没有明说“Debug 和 Release 永远绝对不能混搭,但从经典的ABI(Application Binary Interface)实践去思考该问题不难得出:
对于跨传递二进制实现的C++对象,若二者它们内部有各自不同方式的代码实现(release由于优化更激进甚至连某些内部状态都会直接省略,而debug虽然没什么优化但代码实现上肯定有设置更多调试安全的捕获机制导致C++对象的裁决策略会有所不同),
不过最致命的还在于对象自身内存布局有所不同则必然会破坏不同VC运行库之间的二进制兼容!


1. 当然在实际工程中实在是没有那个库的源代码而只有release版本的静态库lib,那我Debug工程下还能使用吗?
假定都是引用C/C++运行时动态库,且都是VS2015之后的(待会会注重分析VS2015之后的变化,虽然VS2015 之后ABI 稳定了,但 Debug STL 和 Release STL跨模块搞其对象传参高概率炸),
只要满足:公开接口尽可能都是仅C的平凡结构体或复杂对象只对外暴露提供其指针、没有跨模块分配/释放内存、满足所有资源谁创建谁销毁的原则,这个出问题的概率就会极大减少了!
注意对于这种Debug的项目引入Release版的静态库时在链接的时候还会报警告,属于正常现象(只要你确定跑起来没有出错的话):
再来,只不过对比纯Debug版,当程序出错时你引入的那个release版的lib它就不能很好通过PDB定位到报错的源代码具体行号中(不过事实上你本身可能就没有源代码,那有没有都一样,但不管怎么说Debug版会有更多安全捕获措施让报错即时发现,而release版则不一定,可能本身还会带着错误让程序继续往下跑)
关于PDB配合调试器进行异常定位参考:PDB符号定位源代码的调试技巧.mht、CDB自动化AI调试定位异常行号.mht
2. 不过反之地,那主程是release版本还能用debug的库吗?
因此在白易中是被允许生成仅含release版本的lib模块的发布:
(下面演示32位的release+release_static版引入后的情况)
附(对项目加速编译的技巧):使用VC预编译头加速编译.mht、使用多核编译.mht
之后的话,下面的讨论仅限于同debug或同release下对于模块跨VC版本参与工程编译/链接还会面临什么样的问题。
这个问题和前一个“/MD vs /MT”的性质完全不同——
VS2017 vs VS2019 属于 toolset 版本差异,而不是 CRT 模型差异,结论先给出:
这不是经验结论,而是 微软官方明确保证的 ABI兼容性 策略。
从 Visual Studio 2015 开始:2015、2017、2019、2022... 构建的应用与运行时库是二进制兼容的(注意是允许向前兼容,不允许向后兼容,即如:VS2019编译的库给VS2017去用可能会失败)
https://learn.microsoft.com/en-us/lifecycle/faq/visual-c-faq
然后还有个针对C/C++运行时一个重要的事实,无论是VS2015还是VS2017或更高版本的VC编译出的程序,它们均共享同一主版本运行时:vcruntime140.dll、ucrtbased.dll

上面这里是由VS2017编译的debug版你所看到的,根据查壳识别查出的编译器信息仅是MSVC14.0(事实上这个就是VS2015),而链接器信息是14.16这说明它是14.1x属于VS2017的范畴,而它依赖导入的是vcruntime都是140的版本(这个你就算在VS2026上编译也是一样的依赖)
但如果你把它编译为release版你还会看到:
好像把ucrtbased.dll拆分为了各个 api-ms-win-crt-... 等一堆补丁?
限于篇幅有限,这里拆分到文章 VC140之后的VC++运行时.mht 单独讲解。
总而言之,对于模块声明了跨不同(VS2015之后)的动态库通常是兼容的!
注意之前上边说的是动态库的情况,如果只是动态库那么因为其系统采用的动态加载技术实际在同一个进程中只有一份模块副本(当然这么说可能不准确,事实上DLL的概念是全局共享,准确点DLL在加载后在一开始系统整个的物理内存中只读部分的始终只有一份内存,而给到进程的是读时共享而写时复制,当然这个复制也只是依照同个进程而言,所以本质上也没有超出一个进程共享同一份VC运行时实现库的范畴)
故VC140之后的动态库这种一般不会影响多个CRT的冲突(因为基础功能就一定是vcruntime140.dll,而扩展功能再单独给到vcruntime140_X.dll去加载实现)
之前我们说过,无论lib编译为何种,它里边一定不会包含任何CRT的代码实现,所以它们只是声明依赖上的不同,
但现在问题来了,如果lib库声明的是v141的VC静态库(VS2017),而主项目在VS2019中开发而声明的是v142的VC静态库,那实际链接时会发生什么?
然后再看AI对此的解释:

实际上任何的obj中它并不会记载声明依赖的是哪个VC库版本,而仅仅只是提供给链接器这句话:
之前我们还提到过,VS2015之后静态库的名字基本上是统一的(详情VC140之后的VC++运行时.mht#static),而且从功能上新版一定包含旧版的功能且ABI统一兼容,也就是说VS2017的静态库一定是VS2019静态库的子集,所以这里并不存在说会有两份CRT问题。
AI继续解释到:

Visual Studio 版本之间的 C++ 二进制兼容性:https://learn.microsoft.com/en-us/cpp/porting/binary-compat-2015-2017?view=msvc-170
所以对此需要担心的问题就只是开启全程序优化/链接时延迟生成代码(LTCG)这类技术,
白易模块初始项目模板在release版中默认已经把[全程序优化]的选项给关掉了:

这主要是让release模块避免跨VS版本的ABI兼容问题!



到我说了,由于摆脱了统一的链接期的层次,依赖动态库的那种一定是后期才加载系统的,所以运行时真正一定会存在两份完全不同且不可预测的CRT副本



最后还有一种组合:
lib编译的依赖VC动态库是VS2017而主项目VS2019是依赖静态库,其实情况也跟本节标题所讨论的问题是一样的,都是不安全行为。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
当然我知道你想说按照debug和release那样搞纯C接口交互似乎就没问题了?
那我还想再给你分享一下关于全局CRT状态的注意点——关于微软官方链接中:
https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170
有一段话明确指出:
还有一篇文章指出CRT中的全局状态:
https://learn.microsoft.com/en-us/cpp/c-runtime-library/global-state?view=msvc-170
虽然不同CRT段中的静态库(VC140之后)可能本身没有什么ABI问题,但CRT在进行设置的时候它们维护的一些全局状态可能存在“多份应用级副本”
下面是微软官方列举出了可能的CRT全局态受影响的C库函数
关于这个问题我后续也再做了实验:
主程为CRT动态库引入不同VS的动态库但其自身静态CRT的模块.mht(周目二)