运行时库提供了很多变量(包括常量),还有很多类函数,比如字符串处理、输入与输出等。它们可以被静态编译到可执行文件(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后
第二次分配堆块B后
第三次分配堆块C后
释放堆块C后,情况如“第二次分配堆块B后”。
安全检测
在释放堆块时,运行时库会检测当前释放的堆块是否与当前运行时库的__acrt_first_block相等,如果相等,则继续释放;如果不相等,则断言失败。
观察上一节的代码,因为DLL返回的str字符串在EXE的代码空间里(领空)被释放,所以EXE的运行时库会去检查字符串的堆块是否与EXE运行时库的__acrt_first_block相等。但由于这个堆块是DLL的运行时库分配的,所以该堆块与DLL运行时库的__acrt_first_block相等,与EXE运行时库的__acrt_first_block不相等。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-8-7 13:58
被coneco编辑
,原因: