带你跳出坑爹的Runtime Library坑

上一篇文章带你玩转Visual Studio——带你理解微软的预编译头技术我们了解了微软的预编译头技术,预编译的方式让我们的工程编译的更加快速;本篇文章将继续介绍微软的另一项技术,也就是运行时库Runtime Library。

在Windows下进行C++的开发,不可避免的要与Windows的底层库进行交互,然而VS下的一项设置MT、MTd、MD和MDd却经常让人搞迷糊,相信不少人都被他坑过,特别是你工程使用了很多第三库的时候,及容易出现各种链接问题。看一下下面这个错误提示:

LIBCMT.lib(_file.obj) : error LNK2005: ___initstdio already defined in libc.lib(_file.obj) LIBCMT.lib(_file.obj) : error LNK2005: ___endstdio already defined in libc.lib(_file.obj)

有多少人被这玩意坑过,被坑过的请举脚!哈哈……

既然这里这么容易出问题,我们就有必要对其进行深入的了解,知其然且知其所以然才能万事无惧!

什么是Runtime Library?

Runtime Library就是运行时库,也简称CRT(C Run Time Library)。是程序在运行时所需要的库文件,通常运行时库是以Lib或Dll形式提供的。

Windows下C Runtime Library是微软对C标准库函数的实现,这样每个程序可以直接使用C标准库的函数;后来出现了C++,,于是又在C Runtime Library基础上开发了C++ Runtime Library,实现了对C++标准库的支持。因此现在Windows下的C/C++运行时库既包含子C标准库,也包含了C++标准库。如果你安装了VS2010,在安装目录下的VC\crt\src下(如我的目录是C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src)有运行时库(CRT)的源代码,这里既有C的文件(如output.c、stdio.h等),也有C++的文件(如iostream、string)。

在C Runtime Library出现之前,许多程序都使用C编写,而这些程序都要使用标准的C库,按照以前的方式每一个程序最终都要拷贝一份标准库的实现到程序中,这样同一时刻内存中可能有许多份标准库的代码(一个程序一份),所以微软出于效率的考虑把标准C库做为动态链接来实现,这样多个程序使用C标准库时内存中就只有一份拷贝了。

确切地说运行时库指的就是对这些底层的基础功能实现的动态库(Dll),运行时库和普通的Dll一样,只有程序用到了它才会被加载,没有程序使用的时候不会驻留内存的。话虽如此,但有多少系统的东西说不定也是用C写的,这些东西的存在就使C运行时库存在于内存中了,所以运行时库几乎总是需要的。虽然说运行时库应该是动态库,但习惯上我们把与动态运行时库相同代码编译出来的静态库也称为运行时库,因此VC++下的运行时库有ML、MLd、MT、MTd、MD、MD六种(这个后面会讲)。

运行时库的主要作用

提供C标准库(如memcpy、printf、malloc等)、C++标准库(STL)的支持。

应用程序添加启动函数,启动函数的主要功能为将要进行的程序初始化,对全局变量进行赋初值,加载用户程序的入口函数。

 不采用宽字符集的控制台程序的入口点为mainCRTStartup(void)。下面我们以该函数为例来分析运行时库究竟为我们添加了怎样的入口程序。这个函数在crt0.c中被定义,下列的代码经过了笔者的整理和简化:

void mainCRTStartup(void){ int mainret; /*获得WIN32完整的版本信息*/ _osver = GetVersion(); _winminor = (_osver >> 8) & 0x00FF ; _winmajor = _osver & 0x00FF ; _winver = (_winmajor << 8) + _winminor; _osver = (_osver >> 16) & 0x00FFFF ; _ioinit(); /* initialize lowio */ /* 获得命令行信息 */ _acmdln = (char *) GetCommandLineA(); /* 获得环境信息 */ _aenvptr = (char *) __crtGetEnvironmentStringsA(); _setargv(); /* 设置命令行参数 */ _setenvp(); /* 设置环境参数 */ _cinit(); /* C数据初始化:全局变量初始化,就在这里!*/ __initenv = _environ; mainret = main( __argc, __argv, _environ ); /*调用main函数*/ exit( mainret );}

从以上代码可知,运行库在调用用户程序的main或WinMain函数之前,进行了一些初始化工作。初始化完成后,接着才调用了我们编写的main或WinMain函数。只有这样,我们的C语言运行时库和应用程序才能正常地工作起来。

除了crt0.c外,C运行时库中还包含wcrt0.c、 wincrt0.c、wwincrt0.c三个文件用来提供初始化函数。wcrt0.c是crt0.c的宽字符集版,wincrt0.c中包含 windows应用程序的入口函数,而wwincrt0.c则是wincrt0.c的宽字符集版。

MT、MTd、MD、MDd、(ML、MLd 已废弃)的区别与原理

我们可以在Properties->Configuration Properties->C/C++->Code Generation->Runtime Library中设置采用的运行时库的类型。

Runtime Library

在带你玩转Visual Studio——带你发布自己的工程库一文中已经详细讲解了静态库(Lib)与动态库(Dll)的区别。我们知道编译出的静态库只有一个ProjectName.lib文件,而编译出的动态库有两个文件:ProjectName.lib+ProjectName.dll,一个是导入库,一个动态库。

VC++中有六种Runtime Library的类型:

类型 简称 含义 对应的库名称 备注

Single-Threaded /ML Release版的单线程静态库 libc.lib VS2003以后被废弃

Single-Threaded Debug /MLd Debug版的单线程静态库 libcd.lib VS2003以后被废弃

Multi-threaded /MT Release版的多线程静态库 libcmt.lib

Multi-threaded Debug /MTd Debug版的多线程静态库 libcmtd.lib

Multi-threaded DLL /MD Release版的多线程动态库 msvcrt.lib+msvcrtxx.dll

Multi-threaded DLL Debug MDd Debug版的多线程动态库 msvcrtd.lib+msvcrtxxd.dll

(1). 静态链接的单线程库 静态链接的单线程库只能用于单线程的应用程序, C 运行时库的目标代码最终被编译在应用程序的二进制文件中。通过 /ML 编译选项可以设置 Visual C++ 使用静态链接的单线 程库。 (2). 静态链接的多线程库 静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过 /MT 编译选项可以设置 Visual C++ 使用静态链接的多线程库。 该选项生成的可执行文件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。当某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,最终将导致致命的“Invalid Address specified to RtlValidateHeap”问题。 (3). 动态链接的运行时库 动态链接的运行时库将所有的 C 库函数保存在一个单独的动态链接库 MSVCRTxx.DLL 中, MSVCRTxx.DLL 处理了多线程问题。使用 /MD 编译选项可以设置 Visual C++ 使用动态。 链接时将按照传统VC链接dll的方式将运行时库MSVCRxx.DLL的导入库MSVCRT.lib链接,在运行时要求安装了相应版本的VC运行时库可再发行组件包(当然把这些运行时库dll放在应用程序目录下也是可以的)。 因/MD和/MDd方式不会将运行时库链接到可执行文件内部,可有效减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。

如果寒暄只是打个招呼就了事的话,那与猴子的呼叫声有什么不同呢?事实上,

带你跳出坑爹的Runtime Library坑

相关文章:

你感兴趣的文章:

标签云: