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個運行時庫。如下表:

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  

  2.C運行時庫的作用

  C運行時庫除了給我們提供必要的庫函數調用(如memcpy、printf、malloc等)之外,它提供的另一個最重要的功能是爲應用程序添加啓動函數。

  C運行時庫啓動函數的主要功能爲進行程序的初始化,對全局變量進行賦初值,加載用戶程序的入口函數。

  不採用寬字符集的控制檯程序的入口點爲mainCRTStartup(void)。下面我們以該函數爲例來分析運行時庫究竟爲我們添加了怎樣的入口程序。這個函數在crt0.c中被定義,下列的代碼經過了筆者的整理和簡化:
  1. 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.  _ioinit(); /* initialize lowio */

  11.  /* 獲得命令行信息 */
  12.  _acmdln = (char *) GetCommandLineA();

  13.  /* 獲得環境信息 */
  14.  _aenvptr = (char *) __crtGetEnvironmentStringsA();

  15.  _setargv(); /* 設置命令行參數 */
  16.  _setenvp(); /* 設置環境參數 */

  17.  _cinit(); /* C數據初始化:全局變量初始化,就在這裏!*/

  18.  __initenv = _environ;
  19.  mainret = main( __argc, __argv, _environ ); /*調用main函數*/

  20.  exit( mainret );
  21. }
複製代碼
從以上代碼可知,運行庫在調用用戶程序的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++,並在重裝在時選中安裝運行庫源代碼選項。

  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. #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.  file.Write(str,str.GetLength());
  19.  file.Close();
  20. }
複製代碼
我們在"rebuild all"的時候發生了link錯誤:
  1. 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.
複製代碼
發生錯誤的原因在於Visual C++對控制檯程序默認使用單線程的靜態鏈接庫,而MFC中的CFile類已暗藏了多線程。我們只需要在Visual C++6.0中依次點選Project->Settings->C/C++菜單和選項,在Project Options裏修改編譯選項即可。

C 運行時庫是微軟對標準C庫函數的實現,因爲當時考慮到許多程序都使用C編寫,而這些程序都要使用標準的C庫,按照以前的方式每一個程序最終都要拷貝一份標準庫的實現到程序中,這樣同一時刻內存中可能有許多份標準庫的代碼(一個程序一份),所以微軟出於效率的考慮把   標準C庫做爲動態鏈接來實現,這樣多個程序使用C標準庫時內存中就只有一份拷貝了。(對每一個程序來說,它相當於自己擁有一份,   對於標準庫中的全局變量也做了處理的,不會因爲共享同一份代碼而出現衝突)。   這也算是對C標準庫的一個擴展吧,至於說靜態鏈接的時候仍然把它叫做運行時庫那隻能說這是個習慣問題而已了。  
   
  運行時庫和普通的   dll   一樣,如果有程序用到了纔會加載,沒有程序使用的時候不會駐留內存的。話雖如此,但有多少系統的東西說不定也是用C寫的,這些東西的存在就使C運行時庫存在於內存中了

從字面上看,運行庫是程序在運行時所需要的庫文件。通常運行庫是以DLL形式提供的。 Delphi和C++ Builder的運行庫爲.bpl文件,實際還是一個DLL。運行庫中一般包括編程時常用的函數,如字符串操作、文件操作、界面等內容。不同的語言所支持的函數通常是不同的,所以使用的庫也是完全不同的,這就是爲什麼有VB運行庫、C運行庫、Delphi運行庫之分的原因。即使都是C++語言,也可能因爲提供的函數不同,而使用不同的庫。如VC++使用的運行庫和C++ Builder就完全不同。
如果不使用運行庫,每個程序中都會包括很多重複的代碼,而使用運行庫,可以大大縮小編譯後的程序的大小。但另一方面,由於使用了運行庫,所以在分發程序時就必須帶有這些庫,比較麻煩。如果在操作系統中找不到相應的運行庫程序就無法運行。爲了解決這個矛盾,Windows總是會帶上它自己開發的軟件的最新的運行庫。象Windows 2000以後的版本都包括Visual Basic 5.0/6.0的庫。Internet Explorer總是帶有最新的Visual C++ 6.0的庫。Windows XP帶有Microsoft .NET 1.0(用於VB.NET和C#)的庫。Visual C++、Delphi和C++ Builder允許用戶選擇所編譯得到的程序是否依賴於運行庫。而VB、FoxPro、PowerBuilder、LabWindows/CVI和 Matlab就不允許用戶進行這種選擇,必須依賴於運行庫。
轉自http://club.topsage.com/thread-541343-1-1.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章