1、C/C++程序的編譯過程 ios
首先看一下,C/C++程序的編譯過程: windows
一、源文件通過「預處理」生成擴展的源文件(仍然是.cpp文件); 函數
二、擴展的源文件通過「編譯」生成彙編代碼文件; spa
三、彙編代碼文件通過「組裝(Assembler)」生成中間代碼文件; code
四、中間代碼文件通過「連接」生成可執行文件。 內存
整體來講就是,C/C++首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個動做叫作編譯(Compile)。而後再把大量的 Object File合成執行文件,這個動做叫做連接(Link)。 原型
編譯時,編譯器須要的是語法的正確,函數與變量的聲明的正確。對於後者,一般是你須要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在C/C++文件中),只要全部的語法正確,編譯器就能夠編譯出中間目標文件。通常來講,每一個源文件都應該對應於一箇中間目標文件(.O文件或是.OBJ文件)。 編譯器
連接時,主要是連接函數和全局變量,因此,咱們可使用這些中間目標文件(.O文件或是.OBJ文件)來連接咱們的應用程序。連接器並無論函數所在的源文件,只管函數的中間目標文件(Object File),在大多數時候,因爲源文件太多,編譯生成的中間目標文件太多,而在連接時須要明顯地指出中間目標文件名,這對於編譯很不方便,因此,咱們要給中間目標文件打個包,在Windows下這種包叫「庫文件」(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。上圖中的「Object Code For Library Functions」,指的就是這些庫文件中的中間代碼。 io
2、C/C++程序是如何編譯的 編譯
C++支持函數重載,C語言不支持。函數被C++編譯後在符號庫中的名字與C語言的不一樣。例如,假設某個函數的原型爲:void foo( int x, int y );該函數被C編譯器編譯後在符號庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不一樣的編譯器可能生成的名字不一樣,可是都採用了相同的機制,生成的新名字稱爲「Mangled Name」)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者爲_foo_int_float。
一樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,咱們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理類似,也爲類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不一樣。
3、extern關鍵字
extern是C/C++語言中代表函數和全局變量做用範圍(可見性)的關鍵字。extern能夠置於變量或者函數前,以表示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其餘模塊中尋找其定義。
extern int a;
僅僅是一個變量的聲明,其並非在定義變量a,並未爲a分配內存空間。變量a在全部模塊中做爲一種全局變量只能被定義一次,不然會出現連接錯誤。
引用一個定義在其它模塊的全局變量或函數(如,全局函數或變量定義在A模塊,B欲引用)有兩種方法,1、B模塊中include模塊A的頭文件。2、模塊B中對欲引用的模塊A的變量或函數從新聲明一遍,並在聲明前面加extern關鍵字。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,可是並不會報錯。它會在連接階段中從模塊A編譯生成的目標代碼中找到此函數。可是必定要避免使用第二種方式。
4、extern "C"
首先要明確一點,extern "C"是C++中的關鍵字,C語言是不支持extern "C"的。
咱們來看一個例子,在VC++ 6.0中新建一個工程,在工程中編寫兩個文件,分別是a.c和b.cpp。代碼以下:
//file a.c int foo(int x, int y) { return x+y; }
//file b.cpp #include<iostream> #include<windows.h> using namespace std; extern int foo(int x, int y); int main() { int z = foo(1, 3); cout<<z<<endl; system("pause"); }
而後咱們編譯整個工程,編譯器報錯,LNK2001和LNK1120,沒法解析的外部符號,工程出現了連接錯誤。緣由就是咱們上文提到的,a.c文件是按照C語言的方式進行編譯的,foo函數編譯後生成的符號爲_foo;而b.cpp是按照C++方式編譯的,在連接時,b.cpp想要找到的符號是_foo_int_int,因此編譯器會報錯,「沒法解析的外部符號」。
若是咱們把b.cpp中的extern int foo(int x, int y)用extern "C"修飾則會出現正確的結果,以下:
//file b.cpp #include<iostream> #include<windows.h> using namespace std; extern "C" { int foo(int x, int y); } int main() { int z = foo(1, 3); cout<<z<<endl; system("pause"); }
extern "C" 告訴編譯器,foo函數是按照C語言的方式編譯的,不是按照C++方式編譯的,因此在連接時直接去找_foo這樣的符號就好了。
因此,C中編寫的函數若是有在C++中調用的可能,一般會有下面形式的聲明:
#ifdef __cplusplus extern "C" { #endif /**** some declaration or so *****/ #ifdef __cplusplus } #endif
其實extern "C" 的存在就是爲了使C++更好的兼容C。