1.
概论
运行时库是 程序 在运行时所需要的库文件,通常运行时库是以 LIB 或 DLL 形式提供的。 C 运行时库诞生于 20 世纪 70 年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于新观念。所以这个 j时期的 C 运行时库都是单线程的。
运行时库是 程序 在运行时所需要的库文件,通常运行时库是以 LIB 或 DLL 形式提供的。 C 运行时库诞生于 20 世纪 70 年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于新观念。所以这个 j时期的 C 运行时库都是单线程的。
随着
操作系统
多线程技术的发展,最初的
C
运行时库无法满足程序的需求,出现了严重的问题。
C
运行时库使用了多个全局变量(例如
errno
)和静态变量,这可能在多线程程序中引起冲突。假设两个线程都同时设置
errno
,其结果是后设置的
errno
会将先前的覆盖,用户得不到正确的错误信息。
因此, Visual C++ 提供了两种版本的 C 运行时库。一个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库有两个重大差别:
( 1 )类似 errno 的全局变量,每个线程单独设置一个;
这样从每个线程中可以获取正确的错误信息。
( 2 )多线程库中的数据结构以同步机制加以保护。
这样可以避免访问时候的冲突。
Visual C++ 提供的多线程运行时库又分为静态链接库和动态链接库两类,而每一类运行时库又可再分为 debug 版和 release 版,因此 Visual C++ 共提供了 6 个运行时库。如下表:
因此, Visual C++ 提供了两种版本的 C 运行时库。一个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库有两个重大差别:
( 1 )类似 errno 的全局变量,每个线程单独设置一个;
这样从每个线程中可以获取正确的错误信息。
( 2 )多线程库中的数据结构以同步机制加以保护。
这样可以避免访问时候的冲突。
Visual C++ 提供的多线程运行时库又分为静态链接库和动态链接库两类,而每一类运行时库又可再分为 debug 版和 release 版,因此 Visual C++ 共提供了 6 个运行时库。如下表:
C
运行时库
|
库文件
|
Single thread(static link)
|
libc.lib
|
Debug single thread(static link)
|
libcd.lib
|
MultiThread(static link)
|
libcmt.lib
|
Debug multiThread(static link)
|
libcmtd.lib
|
MultiThread(dynamic link)
|
msvert.lib
|
Debug multiThread(dynamic link)
|
msvertd.lib
|
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 ); } |
3.
各种
C
运行时库的区别
( 1 )静态链接的单线程库
静态链接的单线程库只能用于单线程的应用 程序 , C 运行时库的目标代码最终被编译在应用程序的二进制文件中。通过 /ML 编译选项可以设置 Visual C++ 使用静态链接的单线程库。
( 2 )静态链接的多线程库
静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过 /MD 编译选项可以设置 Visual C++ 使用静态链接的单线程库。(?)
( 3 )动态链接的运行时库
动态链接的运行时库将所有的 C 库函数保存在一个单独的动态链接库 MSVCRTxx.DLL 中, MSVCRTxx.DLL 处理了多线程问题。使用 /ML 编译选项可以设置 Visual C++ 使用动态链接的运行时库。
/MDd 、 /MLd 或 /MTd 选项使用 Debug runtime library( 调试版本的运行时刻函数库 ) ,与 /MD 、 /ML 或 /MT 分别对应。 Debug 版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,加强了对错误的检测,因此在运行性能方面比不上 Release 版本。
下面看一个未正确使用 C 运行时库的控制台程序:
( 1 )静态链接的单线程库
静态链接的单线程库只能用于单线程的应用 程序 , C 运行时库的目标代码最终被编译在应用程序的二进制文件中。通过 /ML 编译选项可以设置 Visual C++ 使用静态链接的单线程库。
( 2 )静态链接的多线程库
静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过 /MD 编译选项可以设置 Visual C++ 使用静态链接的单线程库。(?)
( 3 )动态链接的运行时库
动态链接的运行时库将所有的 C 库函数保存在一个单独的动态链接库 MSVCRTxx.DLL 中, MSVCRTxx.DLL 处理了多线程问题。使用 /ML 编译选项可以设置 Visual C++ 使用动态链接的运行时库。
/MDd 、 /MLd 或 /MTd 选项使用 Debug runtime library( 调试版本的运行时刻函数库 ) ,与 /MD 、 /ML 或 /MT 分别对应。 Debug 版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,加强了对错误的检测,因此在运行性能方面比不上 Release 版本。
下面看一个未正确使用 C 运行时库的控制台程序:
#include <stdio.h> #include <afx.h> int main() { CFile file; CString str("I love you"); TRY { file.Open("file.dat",CFile::modeWrite | CFile::modeCreate); } CATCH( CFileException, e ) { #ifdef _DEBUG afxDump << "File could not be opened " << e->m_cause << "/n"; #endif } END_CATCH file.Write(str,str.GetLength()); file.Close(); } |
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex main.exe : fatal error LNK1120: 2 unresolved externals Error executing cl.exe. |
发生错误的原因在于Visual C++对控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程。我们只需要在Visual C++6.0中依次点选Project->Settings->C/C++菜单和选项,在Project Options里修改编译选项即可。 我们在"rebuild all"的时候发生了link错误: 从以上代码可知,运行库在调用用户程序的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的宽字符集版。
Visual C++的运行时库源代码缺省情况下不被安装。如果您想查看其源代码,则需要重装Visual C++,并在重装在时选中安装运行库源代码选项。 2.C 运行时库的作用
C 运行时库除了给我们提供必要的库函数调用(如 memcpy 、 printf 、 malloc 等)之外,它提供的另一个最重要的功能是为应用程序添加启动函数。
C 运行时库启动函数的主要功能为进行程序的初始化,对全局变量进行赋初值,加载用户程序的入口函数。
不采用宽字符集的控制台程序的入口点为 mainCRTStartup(void) 。下面我们以该函数为例来分析运行时库究竟为我们添加了怎样的入口程序。这个函数在 crt0.c 中被定义,下列的代码经过了笔者的整理和简化: