首页
社区
课程
招聘
[原创]Runtime运行时库在MT与MD之间的差别
发表于: 2021-8-7 12:09 8734

[原创]Runtime运行时库在MT与MD之间的差别

2021-8-7 12:09
8734

运行时库提供了很多变量(包括常量),还有很多类函数,比如字符串处理、输入与输出等。它们可以被静态编译到可执行文件(EXE或DLL),也可以被动态加载。

详细的说明可参考C runtime library (CRT) reference - Microsoft Docs。关于用到的库可参考C runtime (CRT) and C++ Standard Library (STL) .lib files

本文重点阐述运行时库在MT和MD之间的区别。

不管编译DLL还是EXE,都可以在VS中设置运行时库。

设置路径为工程属性->Configuration Properties->C/C++->Code Generation->Runtime Library

运行时库分4类:

MTd(Multi-threaded Debug)

MDd(Multi-threaded Debug DLL)

MT (Multi-threaded)

MD (Multi-threaded DLL)

其中最后带d的为Debug版本的运行时库,不带d的为Release版本的运行时库。MTd和MT的T代表静态库,MDd和MD的D代表动态库。

也就是说采用MTd或MT,运行时库会被编译进EXE或DLL里;而MDd或MD的情况下,在EXE启动或DLL被加载时,运行时库会作为DLL被动态载入。

因为DLL和EXE编译时采用MT(d)或MD(d)的结果是一样的,所以这里以EXE编译时采用MT或MD为例。

EXE编译时依赖了其他库,这个库是静态库(x.lib)

在EXE采取MT运行时库时,静态库(x.lib)的编译也必须是MT。因为静态库的原因,EXE只需要把自己需要的代码从静态库(x.lib)提取出来就行,不考虑静态库里的运行库。所以这种情况下EXE和静态库使用的运行时库是同一套代码,都是EXE的运行时库。

EXE编译时依赖了其他库,这个库是动态库(x.dll)

在EXE采取MT运行时库时,动态库(x.dll)的编译也必须是MT。因为是动态库,EXE在加载动态库时,是将其全部代码(包括一份运行时库代码)加载进了EXE进程空间,这样EXE在运行时就包含了两套运行时库代码,一个是动态库的,一个是EXE的。

注:虽然EXE和DLL会用各自的运行时库,但它们用的都是进程默认堆(PEB->ProcessHeap),不存在EXE和DLL用的堆不一致的情况(网上有说使用的堆不一样,这种说法是错误的)。

不管EXE依赖的其他库是静态库还是动态库,它们都必须采用MD来编译。这种情况下编译出来的EXE和DLL(或EXE和LIB)都依赖MD运行时库(即VCRUNTIMExx.dll、MSVCPxx.dll、ucrtbase.dll)。因为都依赖MD运行时库,所以EXE和DLL(或EXE和LIB)用的是同一套运行时库。

根据上一节的描述,这种情况下,EXE和DLL会用各自的运行时库,这时会导致一种错误,如下代码所示:

以上代码会导致如下错误:

这个问题的成因请看下一节。

即使运行时库有两份,这两份代码用的堆都是进程默认堆。

运行时库管理堆采用链表的方式,这里以std::string(x86)为例。当定义一个std::string变量(初始值为“hello worldaaaaa”,长度为0x10)时,运行时库会为该字符串从默认进程堆分配堆块。

长度超过0x10,就会分配堆块来存储字符串,若小于0x10,则存在栈上。

重要变量

运行时库管理堆时,有三个比较重要的变量

__acrt_heap,都指向进程默认堆

__acrt_first_block指向最新被分配的堆块(初始值为nullptr)

__acrt_last_block指向最久被分配的堆块(初始值为nullptr)

算法

根据以上算法,每个运行时库会管理各自的堆块(虽然都是从进程默认堆分配的),所以EXE和DLL的运行时库都分别有以上三个重要变量。一般情况下,每个运行时库的__acrt_first_block都不相等,__acrt_last_block同理。

第一次分配堆块A后

分配堆块A

第二次分配堆块B后

分配堆块B

第三次分配堆块C后

分配堆块C

释放堆块C后,情况如“第二次分配堆块B后”。

安全检测

在释放堆块时,运行时库会检测当前释放的堆块是否与当前运行时库的__acrt_first_block相等,如果相等,则继续释放;如果不相等,则断言失败。

观察上一节的代码,因为DLL返回的str字符串在EXE的代码空间里(领空)被释放,所以EXE的运行时库会去检查字符串的堆块是否与EXE运行时库的__acrt_first_block相等。但由于这个堆块是DLL的运行时库分配的,所以该堆块与DLL运行时库的__acrt_first_block相等,与EXE运行时库的__acrt_first_block不相等。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-8-7 13:58 被coneco编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (7)
雪    币: 2089
活跃值: (3933)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
对你的头像很有兴趣
2021-8-7 14:09
0
雪    币: 181
活跃值: (621)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
C++/WinRT
2021-8-7 20:26
0
雪    币: 3322
活跃值: (3918)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
总结:为了使程序能在其他电脑上运行,应该采用mtd或mt
2021-8-7 20:34
0
雪    币: 2466
活跃值: (4561)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学到了,感谢分享!
2021-8-7 22:22
0
雪    币: 4942
活跃值: (4668)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
6
lhxdiao 对你的头像很有兴趣
出自“路人女主的女主之一”
2021-8-9 00:03
0
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
然而mfc扩展dll却似乎无法使用MT
2021-8-9 01:12
0
游客
登录 | 注册 方可回帖
返回
//