C運行時庫函數
C運行時庫函數是指C語言本身支持的一些基本函數,通常是匯編直接實現的。
API函數
API函數是操作系統為方便用戶設計應用程序而提供的實現特定功能的函數,API函數也是C語言的函數實現的。
區別
他們之間區別是:API函數是針對操作系統的,C語言運行時函數則是針對C語言本身的。
·1、運行時庫就是 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 runtime 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。
·6、windows環境下,VC提供的 C run-time Library又分為動態運行時庫和靜態運行時庫。
動態運行時庫
動態運行時庫主要包括:
·DLL庫文件:msvcrt.dll(或 MSVCRTD.DLL for debug build)
·對應的Import Library文件:MSVCRT.LIB(或 MSVCRTD.LIB for debug build)
靜態運行時庫
靜態運行時庫(release版)對應的主要文件包括:
·LIBC.LIB(Single thread static library, retail version)
·LIBCMT.LIB(Multithread static library, retail version)
msvcrt.dll提供幾千個C函數,即使是像printf這么低級的函數都在msvcrt.dll里。其實你的程序運行時,很大一部分時間是在這些運行庫里運行。在你的程序(release版)被編譯時,VC會根據你的編譯選項(單線程、多線程或DLL)自動將相應的運行時庫文件(libc.lib、libcmt.lib或Import Library msvcrt.lib)鏈接進來。
2.C運行時庫的作用
C運行時庫除了給我們提供必要的庫函數調用(如memcpy、printf、malloc等)之外,它提供的另一個最重要的功能是為應用程序添加啟動函數。
C運行時庫啟動函數的主要功能為進行程序的初始化,對全局變量進行賦初值,加載用戶程序的入口函數。
不采用寬字符集的控制臺程序的入口點為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的寬字符集版。
Visual C++的運行時庫源代碼缺省情況下不被安裝。如果您想查看其源代碼,則需要重裝Visual C++,并在重裝在時選中安裝運行庫源代碼選項。
下面看一個未正確使用C運行時庫的控制臺程序:
#include
#include
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 << " ";
#endif
}
END_CATCH
file.Write(str,str.GetLength());
file.Close();
}
我們在"rebuild all"的時候發生了link錯誤:
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里修改編譯選項即可。
C運行庫和C標準庫的關系
C標準庫,顧名思義既然是標準,就是由標準組織制定的。是由“美國國家標準協會(American National Standards Institute,ANSI)”為了規范C語言庫而制定的標準。在最初,各個大學各個公司使用的C語言庫都不盡相同,造成相互移植非常困難,在這個背景下,制定了這個標準。
C運行庫,是和平臺相關的,即和操作系統相關的。它由不同操作系統不同開發平臺提供不同的C運行庫。但是C運行庫的部分實現是基于C標準庫的,即C運行庫是各個操作系統各個開發工具根據自身平臺開發的庫,某種程度上,可以說C運行庫是C標準庫的一個擴展庫,只是加了很多C標準庫所沒有的與平臺相關的或者不相關的庫接口函數。舉例子如:c標準庫的strcpy函數負責字符串的拷貝,但是由于缺少對目地字符串緩沖區大小的控制,極有可能導致緩沖區溢出(大量的緩沖區溢出攻擊都是由于這種漏洞而產生的);相反,Windows提供了能夠實現同樣功能的安全的字符串拷貝函數,減少了緩沖區攻擊的可能,strcpy_s。這些函數是以c運行庫的方式提供的,當然,不同的操作系統,c運行時庫可能不同,但是對c標準庫的支持是完全一致的,也就是說,在不同的操作系統上,使用同一個c標準庫的函數必然產生一致的結果。
C標準庫中提供的有:
l 標準輸入輸出(stdio.h)。
l 文件操作(stdio.h)。
l 字符操作(ctype.h)。
l 字符串操作(string.h)。
l 數學函數(math.h)。
l 資源管理(stdlib.h)。
l 格式轉換(stdlib.h)。
l 時間/日期(time.h)。
l 斷言(assert.h)。
l 各種類型上的常數(limits.h & float.h)。
你寫的程序可以沒有math庫,程序照樣運行,只是不能處理復雜的數學運算,不過如果沒有了C run-time庫,main()就不會被調用,exit()也不能被響應。因為C run-time library包含了C程序運行的最基本和最常用的函數。
如下是C運行庫與C標準庫的關系:
一個C運行庫大致包含了如下功能:
l 啟動與退出:包括入口函數及入口函數所依賴的其他函數等。
l 標準函數:由C語言標準規定的C語言標準庫所擁有的函數實現。(C標準庫)
l I/O:I/O功能的封裝和實現,參見上一節中I/O初始化部分。
l 堆:堆的封裝和實現,參見上一節中堆初始化部分。
l 語言實現:語言中一些特殊功能的實現。
l 調試:實現調試功能的代碼。
操作系統API和C運行庫CRT,C標準庫之間區別
首先,C語言要早于Windows出現,而且C語言實際標準制定的開始時間也要早于Windows(API概念出現的)系統的開發時間。所以Windows系統在開發的時候是完全可以使用C語言的。目前最多的說法是用C和匯編實現的。那么只要用C,就可能用C標準庫。
我們假設兩種情況,一是Windows API的實現包含部分C標準庫函數的功能實現,這就決定了這部分操作系統API的實現是由調用標準庫實現的,那么在發布時需要加入所用到的c標準庫DLL一同發布。
二是微軟的內核(包括API)開發是使用著一個和平臺嚴格相關的C語言的靜態的鏈接庫,這樣不必提供Dll也能開發和發行。而且必然的這個C庫是在匯編的基礎上實現的,也就是說這個庫里面的C函數都是(至少有很大比例)披著C語法的匯編代碼。
要你是微軟,你選擇哪個呢?也許是兩者兼而有之,也許是后者。
一般情況下,我們說C運行庫暗含的意思是哪種平臺哪個開發平臺的C運行庫,
CRT的實現是基于Windows API的,而WindowsAPI的開發也是基于C語言的,但不是或者不一定基于CRT(或者C標準庫)的。
再深一步,雖然CRT是基于操作系統 API實現的,但并不代表所有的CRT封裝了操作系統 API,如一些用戶的權限控制,操作系統線程創建等都不屬于C運行庫,于是對于這些操作我們就不得不直接調用操作系統API或者其他庫。
總結一下,C標準庫就是任何平臺都可以使用的基本C語言庫。而CRT除了將C標準庫加入所屬范圍外,還擴展了與平臺相關的接口庫,這些接口實現根據不同平臺調用不同平臺的操作系統API。
如下圖所示,采用C標準庫編寫的程序可以應用到windows平臺,也可以應用到linux平臺;而用CRT另外與平臺相關的庫函數編寫的應用程序不能跨平臺運行。
