標籤:vs2010 c++ 執行階段程式庫 mt md
上一篇文章帶你玩轉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方式運作時,其內部會採用同一個堆,記憶體管理將被簡化,跨模組記憶體管理問題也能得到緩解。
/MDd 、 /MLd 或 /MTd 選項使用 Debug runtime library( 調試版本的運行時刻函數庫 ) ,與 /MD 、 /ML 或 /MT 分別對應。 Debug 版本的 Runtime Library 包含了調試資訊,並採用了一些保護機制以協助發現錯誤,加強了對錯誤的檢測,因此在運行效能方面比不上 Release 版本。
結論:/MD和/MDd將是潮流所趨,/ML和/MLd方式請及時放棄,/MT和/MTd在非必要時最好也不要採用了。
如何避免這種錯誤
- /MD和/MDd將是潮流所趨,/ML和/MLd方式請及時放棄,/MT和/MTd在非必要時最好也不要採用了。盡量使用/MD、/MDd這種方式,除非有某些特殊的需要,如希望編譯出來的.exe可執行檔不需要依賴執行階段程式庫的.dll;
- 在多工程開發時,所有的工程使用同一種執行階段程式庫。如Utils的Solution下有兩個Project:Utils和UsingUtils,UsingUtils工程要使用Utils工程編譯出來的庫。如果Utils使用了/MDd的方式,UsingUtils也要使用/MDd的方式,否則會報連結錯誤。
如果Utils使用MTd的方式,而UsingUtils使用/MDd的方式,則會出現重定義的錯誤,如:
1>LIBCMTD.lib(setlocal.obj) : error LNK2005: __configthreadlocale already defined in MSVCRTD.lib(MSVCR100D.dll)
1>LIBCMTD.lib(dbgheap.obj) : error LNK2005: __free_dbg already defined in MSVCRTD.lib(MSVCR100D.dll)
1>LIBCMTD.lib(dbgheap.obj) : error LNK2005: __CrtSetCheckCount already defined in MSVCRTD.lib(MSVCR100D.dll)
這是因為Utils使用MTd的方式,包含了libcmtd.lib庫;而UsingUtils使用/MDd的方式,要包含msvcrtd.lib+msvcrtxxd.dll。libcmtd.lib和msvcrtd.lib是用相同代碼編譯的,一個是靜態庫,一個動態庫的匯入庫,同時包含libcmtd.lib和msvcrtd.lib肯定就對相同的函數進行了重複的定義。
- 以Release方式進行編譯時間使用Release的庫,使用Debug的方式編譯時間使用Debug的庫。如編譯Release版本的UsingUtils時,要使用Release方式編譯出來的Utils庫,編譯Debug版本的UsingUtils時,要使用Debug方式編譯出來的庫。
曆史發展的角度講解執行階段程式庫
上一篇回顧:
帶你玩轉Visual Studio——帶你理解微軟的先行編譯頭技術
下一篇要講述的內容:
帶你玩轉Visual Studio——帶你理解多位元組編碼與Unicode碼
(本文未完,明日繼續)
著作權聲明:本文為博主原創文章,未經博主允許不得用於任何商業用途,轉載請註明出處。
帶你玩轉Visual Studio——帶你跳出坑爹的Runtime Library坑