目录
[1. 基础概念](#1. 基础概念)
[2. 直观感受一下静态编译和动态编译的体积与依赖项目](#2. 直观感受一下静态编译和动态编译的体积与依赖项目)
[3. VS运行时库包含哪些主要文件(从VS2015起)](#3. VS运行时库包含哪些主要文件(从VS2015起))
[4. 动态库和静态库混用的情况](#4. 动态库和静态库混用的情况)
[5. 感谢清单](#5. 感谢清单)
1. 基础概念
所谓的运行时库(Runtime Library)就是WINDOWS系统给我们实现了C/C++的库函数的功能,提供的.LIB和.DLL文件。
其中.LIB是静态库,假如我们程序用.LIB去链接的话,意味着这个EXE里就包含了C/C++库函数的实现,运行的时候就不需要再依赖目标电脑上的运行库,方便拿到其他电脑跑。缺点呢,就是程序体积大。
.DLL则是动态库,选择动态链接的时候,EXE需要用到C/C++库函数的实现的时候,会去系统里面找相对应的DLL。多个程序也可以共享这一个DLL,带来的好处就是节省EXE的体积,但是拿到其他电脑去的话可能跑不起来,报错说:"找不到XXXXX.DLL"
我们用visual studio开发的时候,在项目属性里面可以看到有4种运行时库可选
|-------|------|---------|
| 运行库名称 | 链接方式 | 其他 |
| MT | 静态 | |
| MTd | 静态 | Debug版本 |
| MD | 动态 | |
| MDd | 动态 | Debug版本 |
2. 直观感受一下静态编译和动态编译的体积与依赖项目
用vs新建一个工程,简单写一个hello world的程序。
cpp
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World" << endl;
return 0;
}
然后分别用静态生成和动态生成:
由上面两个图片可以清晰地看出来:静态编译体积大,动态编译依赖多
3. VS运行时库包含哪些主要文件(从VS2015起)
|------------|----------------------|-----------------------|----------------------------------------------|------------------------------------------------|
| 库描述 | MT | MTD | MD | MDd |
| 通用C运行时库 | libucrt.lib
| libucrtd.lib
| ucrt.lib
ucrtbase.dll
| ucrtd.lib
ucrtbased.dll
|
| VC运行库 | libvcruntime.lib
| libvcruntimed.lib
| vcruntime.lib
vcruntime<version>.dll
| vcruntimed.lib
vcruntime<version>d.dll
|
| C++标准库 | libcpmt.lib
| libcpmtd.lib
| msvcprt.lib
msvcp<version>.dll
| msvcprtd.lib
msvcp<version>d.dll
|
| 初始化CRT的代码库 | libcmt.lib
| libcmtd.lib
| msvcrt.lib
| msvcrtd.lib
|
参考链接:C 运行时 (CRT) 和 C++ 标准库 (STL) .lib 文件 | Microsoft Learn
仔细留意一下【初始化CRT的代码库】,为什么动态生成的依赖库只有lib++没有dll++,官网说它的dll就是【通用C运行时库】和【VC运行库】的合集。这里有点难理解,也不知道为什么要这么设计呢?希望前辈们多多指教一下。
4. 动态库和静态库混用的情况
假如我们现在EXE使用动态编译,但依赖一个静态编译而成的LIB,可以吗?
试试!首先自己编译一个lib。头文件如下:
cpp
#pragma once
void StaticHello(); // 定义一个函数
源文件如下:
cpp
#include "my_mtd_lib.h"
#include <iostream>
using namespace std;
// 实现头文件的函数
void StaticHello()
{
cout << "----------" << endl;
}
生成选项用调试版的的静态库MTd
接着在其他工程调用这个LIB
cpp
#include <iostream>
using namespace std;
#include "../my-mtd-lib/my_mtd_lib.h"
#pragma comment(lib, "../Debug/my-mtd-lib.lib")
int main()
{
cout << "**********" << endl;
StaticHello(); // 调用my-mtd-lib.lib的函数
return 0;
}
生成选项用调试版的动态库MDd
点生成报错:
cpp
1>libcpmtd.lib(wlocale.obj) : error LNK2005: "public: int __thiscall std::ios_base::flags(void)const " (?flags@ios_base@std@@QBEHXZ) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
1>libcpmtd.lib(wlocale.obj) : error LNK2005: "public: __int64 __thiscall std::ios_base::width(__int64)" (?width@ios_base@std@@QAE_J_J@Z) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
1>libcpmtd.lib(wlocale.obj) : error LNK2005: "public: __int64 __thiscall std::ios_base::width(void)const " (?width@ios_base@std@@QBE_JXZ) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
1>libcpmtd.lib(xlocale.obj) : error LNK2005: "public: int __thiscall std::ios_base::flags(void)const " (?flags@ios_base@std@@QBEHXZ) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
1>libcpmtd.lib(xlocale.obj) : error LNK2005: "public: int __thiscall std::basic_streambuf<char,struct std::char_traits<char> >::sputc(char)" (?sputc@?$basic_streambuf@DU?$char_traits@D@std@@@std@@QAEHD@Z) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
1>libcpmtd.lib(xlocale.obj) : error LNK2005: "public: __int64 __thiscall std::ios_base::width(__int64)" (?width@ios_base@std@@QAE_J_J@Z) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
1>libcpmtd.lib(xlocale.obj) : error LNK2005: "public: __int64 __thiscall std::ios_base::width(void)const " (?width@ios_base@std@@QBE_JXZ) 已经在 msvcprtd.lib(MSVCP140D.dll) 中定义
报的都是同一个错:libcpmtd.lib 与msvcprtd.lib冲突,有符号重定义。再仔细看是不是很眼熟,刚好是前面 ++第三部分++ 表格里面【C++标准库】里面MTd和MDd,跟我们项目的设定一一对应。我对这个报错的理解是:链接阶段,main.obj会找到msvcprtd.lib里面关于cout的符号,也发现my-mtd-lib.lib也带了来自libcpmtd.lib的cout的符号,所以报重定义。
所以一个项目里面像我的例子这样混用两个运行时库,实际上大概率不行,理论上也不好。最优的做法当然是所有依赖都基于同一套运行时库。
5. 感谢清单
这篇文章是受到下面博主的文章指引启发的,感谢前辈的分享