【问题标题】:Compiling C file with /c and /MD options, then linking .lib files使用 /c 和 /MD 选项编译 C 文件,然后链接 .lib 文件
【发布时间】:2023-04-05 08:48:01
【问题描述】:
我有一个使用 3-4 个外部库的 C 文件。它是使用 cl.exe 和 link.exe(MSVC) 构建的。
构建脚本显示生成exe的过程分两个阶段-
- 使用带有
cl.exe 的'/c' 选项来获取.o 文件。还给出了一堆其他标志,但我关心的是/MD。 MSDN 说:
使应用程序使用特定于多线程和特定于 DLL 的运行时库版本。定义 _MT 和 _DLL 并使编译器将库名称 MSVCRT.lib 放入 .obj 文件中。
当我们只编译而不链接时,给/MD 和cl 有什么用?
-
在此之后,link.exe 用于生成 exe。为链接指定了几个 .lib 文件(如 ole32.lib、advapi32.lib、user32.lib 等以及其他非 MSVC 特定的文件)。我的问题是 .lib 不是用于静态链接的文件吗?如果是,那么为什么最终的 exe 只有大约 500 KB? .libs 都不是导入库。
如果不是,那么link 默认使用静态链接还是动态链接?与cl 一起给出的/MD 在这里有什么作用吗?
【问题讨论】:
标签:
c
visual-c++
linker
linker-flags
cl
【解决方案1】:
从未设想过最初的 C 运行时库必须支持由多个模块构建的程序。它包含全局变量,例如errno 和stdout,以及具有隐式全局状态的函数,例如strtok() 和malloc()。您可以将 DLL 与其自己的 CRT 副本链接,但这对您如何设计 DLL 接口提出了相当严格的要求。您必须非常小心,永远不要依赖 CRT 状态。弄错这一点几乎不可能诊断出运行时不当行为。
解决方法是在您的进程中只拥有 一个 CRT 副本。这是 /MD 完成的任务,您最终将依赖于存储在 DLL 中的 CRT 版本。由所有模块共享。像msvcr120.dll,VS2013使用的那个。
编译器需要知道这一点,才能正确使用该 DLL 版本。一个简单的例子是errno,它是一个带有 /MT 的全局变量,但它被宏编成带有 /MD 的函数调用,因此只有 DLL 内的一个全局变量用于跟踪最后一个已知值。如果 /MD 生效,将定义 _DLL 宏,用于编译器的 .h 文件。
另一个副作用是编译器会自动为 msvcrt.lib 或 libcmt.lib 插入链接指令(相当于#pragma 注释)。旨在帮助您避免错误并省略明确给出 CRT 库链接指令的需要。弄错会导致很难诊断链接器错误消息。与您尝试链接使用不匹配的 /MT 和 /MD 构建的 .obj 或 .lib 文件时得到的类型没有什么不同。哪个当然不能正常工作,你不能同时依赖两者。
所有 .lib 都不是导入库
您按名称列出的 ole32.lib、advapi32.lib、user32.lib 实际上是导入库。它们是标准的操作系统 DLL。在运行时,您的程序将加载相应的 DLL,从调试器中很容易看到。对于 VS,您将在“输出”窗口中看到这一点。值得注意的是,这些 DLL 实际上使用的 CRT 与您的程序不同,它们绑定到 c:\windows\system32\msvcrt.dll。 winapi 经过精心设计,绝不会成为问题。
链接默认使用静态链接还是动态链接
没有默认值,它取决于您链接的 .lib。区分静态链接库和导入库。构建 DLL 时会创建一个导入库。它是一个不包含代码的小文件,仅包含 DLL 中导出函数的名称,因此链接器知道在程序的导入表中放置一个条目。 DLL依赖被解析,导入的函数在程序启动时被加载器绑定。