C運行時庫(C Run-time Library)詳解

一、什麼是C運行時庫

1)C運行時庫就是 C run-time library,是 C 而非 C++ 語言世界的概念:取這個名字就是因爲你的 C 程序運行時需要這些庫中的函數.

2)C 語言是所謂的“小內核”語言,就其語言本身來說很小(不多的關鍵字,程序流程控制,數據類型等);所以,C 語言內核開發出來之後,Dennis Ritchie 和 Brian Kernighan 就用 C 本身重寫了 90% 以上的 UNIX 系統函數,並且把其中最常用的部分獨立出來,形成頭文件和對應的 LIBRARY,C run-time library 就是這樣形成的。

3)隨後,隨着 C 語言的流行,各個 C 編譯器的生產商/個體/團體都遵循老的傳統,在不同平臺上都有相對應的 Standard Library,但大部分實現都是與各個平臺有關的。由於各個 C 編譯器對 C 的支持和理解有很多分歧和微妙的差別,所以就有了 ANSI C;ANSI C (主觀意圖上)詳細的規定了 C 語言各個要素的具體含義和編譯器實現要求,引進了新的函數聲明方式,同時訂立了 Standard Library 的標準形式。所以C運行時庫由編譯器生產商提供。至於由其他廠商/個人/團體提供的頭文件和庫函數,應當稱爲第三方 C 運行庫(Third party C run-time libraries)。

4)C run-time library裏面含有初始化代碼,還有錯誤處理代碼(例如divide by zero處理)。你寫的程序可以沒有math庫,程序照樣運行,只是不能處理複雜的數學運算,不過如果沒有了C run-time庫,main()就不會被調用,exit()也不能被響應。因爲C run-time library包含了C程序運行的最基本和最常用的函數。

5)到了 C++ 世界裏,有另外一個概念:Standard C++ Library,它包括了上面所說的 C run-time library 和 STL。包含 C run-time library 的原因很明顯,C++ 是 C 的超集,沒有理由再重新來一個 C++ run-time library. VC針對C++ 加入的Standard C++ Library主要包括:LIBCP.LIB, LIBCPMT.LIB和 MSVCPRT.LIB

二、Visual C++中對運行時庫的支持 

        運行時庫是程序在運行時所需要的庫文件,通常運行時庫是以LIB或DLL形式提供的。C運行時庫誕生於20世紀70年代,當時的程序世界還很單純,應用程序都是單線程的,多任務或多線程機制在此時還屬於新觀念。所以這個時期的C運行時庫都是單線程的。

  隨着操作系統多線程技術的發展,最初的C運行時庫無法滿足程序的需求,出現了嚴重的問題。C運行時庫使用了多個全局變量(例如errno)和靜態變量,這可能在多線程程序中引起衝突。假設兩個線程都同時設置errno,其結果是後設置的errno會將先前的覆蓋,用戶得不到正確的錯誤信息。

  因此,Visual C++提供了兩種版本的C運行時庫。一個版本供單線程應用程序調用,另一個版本供多線程應用程序調用。多線程運行時庫與單線程運行時庫有兩個重大差別:

  (1)類似errno的全局變量,每個線程單獨設置一個;

  這樣從每個線程中可以獲取正確的錯誤信息。

  (2)多線程庫中的數據結構以同步機制加以保護。

  這樣可以避免訪問時候的衝突。

  Visual C++提供的多線程運行時庫又分爲靜態鏈接庫和動態鏈接庫兩類,而每一類運行時庫又可再分爲debug版和release版,因此Visual C++共提供了6個運行時庫。如下表:


Reusable Library Switch Library Macro(s) Defined
Single Threaded /ML LIBC (none)
Static MultiThread /MT LIBCMT _MT
Dynamic Link (DLL) /MD MSVCRT _MT and _DLL
Debug Single Threaded /MLd LIBCD _DEBUG
Debug Static MultiThread /MTd LIBCMTD _DEBUG and _MT
Debug Dynamic Link (DLL) /MDd MSVCRTD _DEBUG, _MT, and _DLL
注:從Visual C++ 2005開始,libcp.lib和libcpd.lib(老的/ML和/MLd選項)已經被移除。通過/MT和/MTd使用libcpmt.lib和libcpmtd.lib取代。

 

 

    /MT和/MTd表示採用多線程CRT庫的靜態lib版本。該選項會在編譯時將運行時庫以靜態lib的形式完全嵌入。該選項生成的可執行文件運行時不需要運行時庫dll的參加,會獲得輕微的性能提升,但最終生成的二進制代碼因鏈入龐大的運行時庫實現而變得非常臃腫。當某項目以靜態鏈接庫的形式嵌入到多個項目,則可能造成運行時庫的內存管理有多份,最終將導致致命的“Invalid Address specified to RtlValidateHeap”問題。另外託管C++和CLI中不再支持/MT和/MTd選項。

    /MD和/MDd表示採用多線程CRT庫的動態dll版本,會使應用程序使用運行時庫特定版本的多線程DLL。鏈接時將按照傳統VC鏈接dll的方式將運行時庫MSVCRxx.DLL的導入庫MSVCRT.lib鏈接,在運行時要求安裝了相應版本的VC運行時庫可再發行組件包(當然把這些運行時庫dll放在應用程序目錄下也是可以的)。 因/MD和/MDd方式不會將運行時庫鏈接到可執行文件內部,可有效減少可執行文件尺寸。當多項目以MD方式運作時,其內部會採用同一個堆,內存管理將被簡化,跨模塊內存管理問題也能得到緩解。

三、MSND上相關說明

MSDN上對運行時庫的相關說明

 

選項         

說明

/MD

使應用程序使用運行庫的多線程並特定於 DLL 的版本。 定義 _MT 和 _DLL,並使編譯器將庫名 MSVCRT.lib 放入 .obj 文件中。

用此選項編譯的應用程序靜態鏈接到 MSVCRT.lib。 此庫提供允許鏈接器解析外部引用的代碼的層。 實際工作代碼包含在 MSVCR100.DLL, 中,該庫必須在運行時對於與 MSVCRT.lib 鏈接的應用程序可用。

/MDd

定義 _DEBUG_MT 和 _DLL,並使應用程序使用運行庫的調試多線程並特定於 DLL 的版本。 它還使編譯器將庫名 MSVCRTD.lib 放入 .obj 文件中。

/MT

使應用程序使用運行庫的多線程靜態版本。 定義 _MT 並使編譯器將庫名 LIBCMT.lib 放入 .obj 文件中,以便鏈接器使用 LIBCMT.lib 解析外部符號。

/MTd

定義 _DEBUG 和 _MT 此選項還使編譯器將庫名 LIBCMTD.lib 放入 .obj 文件中,以便鏈接器使用 LIBCMTD.lib 解析外部符號。

/LD

創建 DLL。

將 /DLL 選項傳遞到鏈接器。 鏈接器查找 DllMain 函數,但並不需要該函數。 如果沒有編寫 DllMain 函數,鏈接器將插入返回 TRUE 的 DllMain 函數。

鏈接 DLL 啓動代碼。

如果命令行上未指定導出 (.exp) 文件,則創建導入庫 (.lib);將導入庫鏈接到調用您的 DLL 的應用程序。

將 /Fe(命名 EXE 文件) 解釋爲命名 DLL 而不是 .exe 文件;默認程序名成爲基名稱.dll 而不是基名稱.exe。

除非顯式指定 /MD,否則將暗指 /MT

/LDd

創建調試 DLL。 定義 _MT 和 _DEBUG


 

MSDN上的警告
     不要混合使用運行時庫的靜態版本和動態版本。在一個進程中有多個運行時庫副本會導致問題,因爲副本中的靜態數據不與其他副本共享。鏈接器禁止在 .exe 文件內部既使用靜態版本又使用動態版本鏈接,但您仍可以使用運行時庫的兩個(或更多)副本。例如,當與用動態 (DLL) 版本的運行時庫鏈接的 .exe 文件一起使用時,用靜態(非 DLL)版本的運行時庫鏈接的動態鏈接庫可能導致問題。(還應該避免在一個進程中混合使用這些庫的調試版本和非調試版本)。

四、舉例

  C運行時庫除了給我們提供必要的庫函數調用(如memcpy、printf、malloc等)之外,它提供的另一個最重要的功能是爲應用程序添加啓動函數
  C運行時庫啓動函數的主要功能爲進行程序的初始化,對全局變量進行賦初值,加載用戶程序的入口函數
  不採用寬字符集的控制檯程序的入口點爲mainCRTStartup(void)。下面我們以該函數爲例來分析運行時庫究竟爲我們添加了怎樣的入口程序。這個函數在crt0.c中被定義:

 

  1. <SPAN style="FONT-SIZE: 18px">void mainCRTStartup(void)  
  2. {  
  3.  int mainret;  
  4.  /*獲得WIN32完整的版本信息*/  
  5.  _osver = GetVersion();  
  6.  _winminor = (_osver >> 8) & 0x00FF ;  
  7.  _winmajor = _osver & 0x00FF ;  
  8.  _winver = (_winmajor << 8) + _winminor;  
  9.  _osver = (_osver >> 16) & 0x00FFFF ;  
  10.  
  11.  _ioinit(); /* initialize lowio */  
  12.  
  13.  /* 獲得命令行信息 */  
  14.  _acmdln = (char *) GetCommandLineA();  
  15.  
  16.  /* 獲得環境信息 */  
  17.  _aenvptr = (char *) __crtGetEnvironmentStringsA();  
  18.  
  19.  _setargv(); /* 設置命令行參數 */  
  20.  _setenvp(); /* 設置環境參數 */  
  21.  
  22.  _cinit(); /* C數據初始化:全局變量初始化,就在這裏!*/  
  23.  
  24.  __initenv = _environ;  
  25.  mainmainret = main( __argc, __argv, _environ ); /*調用main函數*/  
  26.  
  27.  exit( mainret );  
  28. }</SPAN> 

 

從以上代碼可知,運行庫在調用用戶程序的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++,並在重裝在時選中安裝運行庫源代碼選項。

 

下面看一個未正確使用C運行時庫的控制檯程序:

  1. <SPAN style="FONT-SIZE: 18px">#include <stdio.h> 
  2. #include <afx.h> 
  3. int main()  
  4. {  
  5.  CFile file;  
  6.  CString str("I love you");  
  7.  TRY  
  8.  {  
  9.   file.Open("file.dat",CFile::modeWrite | CFile::modeCreate);  
  10.  }  
  11.  CATCH( CFileException, e )  
  12.  {  
  13.   #ifdef _DEBUG  
  14.   afxDump << "File could not be opened " << e->m_cause << "\n";  
  15.   #endif  
  16.  }  
  17.  END_CATCH  
  18.  
  19.  file.Write(str,str.GetLength());  
  20.  file.Close();  
  21. }</SPAN> 

在"rebuild all"的時候發生了link錯誤:

  1. <SPAN style="FONT-SIZE: 18px">nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex  
  2. nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex  
  3. main.exe : fatal error LNK1120: 2 unresolved externals  
  4. Error executing cl.exe.</SPAN> 

 

  發生錯誤的原因在於Visual C++對控制檯程序默認使用單線程的靜態鏈接庫,而MFC中的CFile類已暗藏了多線程。我們只需要在Visual C++6.0中依次點選Project->Settings->C/C++菜單和選項,在Project Options裏修改編譯選項即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章