msvc C++编译链接

发布时间 2023-07-19 18:12:41作者: Backsword

C++编译链接

静态库编译

C RunTimeLibrary

C++是C的超集,C RunTimeLibrary 是 C 标准库,在编译期安装的时候,或者下载vc运行时库安装到电脑中。

msvc中/mt /mtd /md /mdd 是决定当前程序用哪个C RunTimeLibrary. 不同的实现不同。

链接过程

静态库链接过程是需要所有的lib文件。比如 A 静态库有 Hello 函数。 B 静态库使用 A 项目的 Hello 函数编译成 Hello World 函数,C 执行程序调用 B 的Hello World函数。

A.lib 会包含Hello函数的信息。

B.lib 会包含HelloWorld函数的信息。但没有Hello的函数。

C.exe 要链接exe的时候,需要 A.lib B.lib 才能真正链接成功。

当然还有一些默认的,比如 C RunTimeLibrary 的链接,也会参与进来。

当A.lib的 C RunTimeLibrary 与 B.lib 中C RunTimeLibrary的版本不匹配的时候,会报 error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MT_StaticRelease”不匹配值“MTd_StaticDebug”。 (如果没有调用 C 的函数,则不会报这个错,最常见的 #include ... 但是基本函数,只要有一个包含了,就会报错,所以必然出错)

原因是 在链接的过程中,C的函数有多种实现导致。所以同一个函数,无法定位真正是哪个RunTimeLibrary中的函数参与进来。

动态库编译

动态库编译成dll,是跟exe一样需要链接,链接一个可加载的动态库。所以一个dll中,包含所有引用的函数信息。

A.lib 静态库 b.lib b.dll 动态库(动态库项目也会生成一个lib符号文件,但没有实现,因为c++会在编译期间,把函数和类改名。)

b.dll 中就包含 Hello 函数的信息,当链接的时候,只需要链接 b.lib 即可。

c.exe 就只需要b.lib 和 b.dll 即可。

场景问题加深理解

下面场景都是使用了 RuntimeLibrary 库的情景(基本没有不用的)

  1. 因为exe必用 c 标准库的东西,所以链接的时候如果 /MD /MDD /MT /MTD 如果不匹配,都会报 RuntimeLibrary 不一致

  2. 3rd_release.lib -> engine.lib -> app.exe 3rd_release.lib 如果是静态库,并且是release,没有debug版本, 那 engine.lib 必须也是release版本的lib,否则在 app.exe 生成的时候会报 RuntimeLibrary 不一致。

  3. debug 是可以链接 release 版本的代码库的,但是 debug A.exe 不能调用 额外的 debug B 库,B 调用 release.lib 会报 A.exe 的RuntimeLibrary和 release.lib 的不一致。

  4. stl 不同debug release 的 许多变量的长度不一致,运行时会溢出闪退。

  5. *.lib 库对应的宏和头文件使用应保持一致,因为编译时,已经根据宏 导出函数内容到 .lib 中,如果头文件声明跟 lib 中函数不一致,会导致 函数 link 不上。

  6. class 的头文件函数(实现在头文件中), lib 中不会定义,基本大部分编译期,会直接把头文件函数变为内联函数,而且不用dllexport。

  7. 如果想使用 3rd_release.lib -> engine.lib -> app.exe engine 是debug库,可以用 engine.dll ,这样会在dll时候,就链接完成。

  8. 静态库函数保持一致,全局变量,静态变量都是一份。动态库会在链接的时候,把静态库的内容链接到dll中,两个dll链接一个静态库,则这个静态库中的全局变量和静态变量都是两份。如果宏定义不一致,则会爆炸。(可以想象,一个库改了静态变量,而另一个库的函数中没有改,导致实现有时好,有时坏。 心态爆炸)

  9. vs 中如果不填 /md ... 等,填空,会默认 /mt /mtd (不一定,我测试是这样)

  10. cpp中使用宏,头文件不使用的库,已经生成的lib 文件,链接的主体(exe)再怎么改 宏定义,也不会影响函数的实现(在编译的过程已经确定了,链接方再怎么改,也无济于事)

  11. 网上查的dll使用准则是,dll中的对象,不能传递到dll外,如果传出来,可能会调用到跟dll中预想不同的函数(因为dll的宏定义是在dll生成的时候,但是使用的人的dll和stl等标准库的实现不一致),导致整体流程损坏而查不到原因。 dll的对象是在dll中运转(减少错误发生率)

总结

如果没有想好的话,最好是不要使用dll,静态库只是编译慢,但会减少很多问题。 如果想要使用dll,则一定要注意项目链接和宏,还有对象的流转,还有dll链接的问题。

最好是,如果一个dll链接了一个静态库,则不导出这个静态库的任何头文件,所有想要操作这个静态库的,都需要通过dll的接口来调用。

gcc clang 等编译器,有些没有_DEBUG 宏,当跨编译器链接的时候,也可能会出现额外的问题。

而有些cmake工具会重写宏定义,如果通过vs创建项目,有些会定义默认的宏定义,但是cmake等工具,是直接写文件。导致不一定会跟vs等IDE一样生成默认的宏定义。则会导致编译的问题。

目前C++项目第三方都是开源的,或者会给debug版本,所以大部分都是自己公司的某个人写的(或者哪里编译出来的)才会导致只有release库文件。大多数是没有这个问题的。