C++編程語言是C語言的一種擴展形式。許多C語言和匯編語言接口的基本規(guī)則同樣適用于C++。但是,有一些規(guī)則需要修正。同樣,擁有一些匯編語言的知識,你能很容易理解C++中的一些擴展部分。這一節(jié)假定你已經(jīng)有一定的C++基礎知識。
C++允許不同的函數(shù)(和類成員函數(shù))使用同樣的函數(shù)名來定義。當不止一個函數(shù)共享同一個函數(shù)名時,這些函數(shù)就稱為重載函數(shù)。在C語言中,如果定義的兩個函數(shù)使用的函數(shù)名是一樣,那么連接器將產(chǎn)生一個錯誤,因為在它連接的目標文件中,一個符號它將找到兩個定義。例如,考慮圖7.10中的代碼。等價的匯編代碼將定義兩個名為_f的標號,而這明顯是錯誤的。
C++使用和C一樣的連接過程,但是通過執(zhí)行名字改編或修改用來標記函數(shù)的符號來避免這個錯誤。在某種程序上,C也早已經(jīng)使用了名字改編。當創(chuàng)建函數(shù)的標號時,它在C函數(shù)名上增加了一條下劃線。但是,C語言將以同樣的方法來改編圖7.10中的兩個函數(shù)名,那么將會產(chǎn)生一個錯誤。C++使用一個更高級的改編過程:為這些函數(shù)產(chǎn)生兩個不同的標號。例如:圖7.10中的第一個函數(shù)將由DJGPP指定為標號_f_ _Fi,而第二個函數(shù),指定為_f __Fd。這樣就避免了任何的連接錯誤。
不幸的是,關于在C++中如何改編名字并沒有一個標準,而且不同的編譯器改編的名字也不一樣。例如,Borland C++將使用標號@f$qi和@f$qd來表示圖7.10中的兩個函數(shù)。但是,規(guī)則并不是完全任意的。改編后的名字編碼成函數(shù)的簽名。一個函數(shù)的簽名是通過它攜帶的參數(shù)的順序和類型來定義的。注意,,攜帶了一個int參數(shù)的函數(shù)在它的改編名字的末尾將有
一個i(對于DJGPP 和Borland都是一樣),而攜帶了一個double參數(shù)的函數(shù)在它的改編名字的末尾將有一個d。如果有一個名為f的函數(shù),它的原型如下:
void f ( int x, int y, double z );
DJGPP將會把它的名字改編成_f_ _Fiid而Borland將會把它改編成@f$qiid。函數(shù)的返回類型并不是函數(shù)簽名的一部分,因此它也不會編碼到它的改編名字中。這個事實解釋了在C++中的一個重載規(guī)則。只有簽名唯一的函數(shù)才能重載。就如你能看到的,如果在C++中定義了兩個名字和簽名都一樣的函數(shù),那么它們將得到同樣的簽名,而這將產(chǎn)生一個連接錯誤。
缺省情況下,所有的C++函數(shù)都會進行名字改編,甚至是那些沒有重載的函數(shù)。它編譯一個文件時,編譯器并沒有方法知道一個特定的函數(shù)重載與否,所以它將所有的名字改編。事實上,和函數(shù)簽名的方法一樣,編譯器同樣通過編碼變量的類型來改編全局變量的變量名。因此,如果你在一個文件中定義了一個全局變量為某一類型然后試圖在另一個文件中用一個錯誤的類型來使用它,那么將產(chǎn)生一個連接錯誤。C++這個特性被稱為類型安全連接。它同樣暴露出另一種類型的錯誤:原型不一致。當在一個模塊中函數(shù)的定義和在另一個模塊使用的函數(shù)原型不一致時,就發(fā)生這種錯誤。在C中,這是一個非常難調(diào)試出來的問題。C并不能捕捉到這種錯誤。程序將被編譯和連接,但是將會有未定義的操作發(fā)生,就像調(diào)用的代碼會將和函數(shù)期望不一樣的類型壓入棧中一樣。在C++中,它將產(chǎn)生一個連接錯誤。
當C++編譯器語法分析一個函數(shù)調(diào)用時,它通過查看傳遞給函數(shù)的參數(shù) 的類型來尋找匹配的函數(shù)。如果它找到了一個匹配的函數(shù),那么通過使用 編譯器的名字改編規(guī)則,它將創(chuàng)建一個CALL來調(diào)用正確的函數(shù)。 因為不同的編譯器使用不同的名字改編規(guī)則,所以不同編譯器編譯 的C++代碼可能不可以連接到一起。當考慮使用一個預編譯的C++庫時, 這個事實是非常重要的!如果有人想寫出一個能在C++代碼中使用的匯編 程序,那么他必須知道要使用的C++編譯器使用的名字改編規(guī)則(或使用下 面將解釋的技術)。 機敏的學生可能會詢問在圖 7.10中的代碼到底能不能如預期般工作。 因為C++改編了所有函數(shù)的函數(shù)名,那么printf將被改編,而編譯器將不 會產(chǎn)生一個到標號 printf處的CALL調(diào)用。這是一個非常正確的擔憂!如 果printf的原型被簡單地放置在文件的開始部分,那么這就將發(fā)生。原型為:
int printf ( const char ?, ...);
DJGPP將會把它改編為_printf_ _FPCce。(F表示function ,函數(shù) ,P表示pointer, 指針 ,C表示const ,常量 ,c表示char而e表示省略號。)那么它將不會調(diào)用 正規(guī)C庫中的printf函數(shù)!當然,必須有一種方法讓C++代碼用來調(diào)用C代 碼。這是非常重要的,因為到處都有 許多 非常有用的舊的C代碼。除了允許 你調(diào)用遺留的C代碼外,C++同樣允許你調(diào)用使用了正規(guī)的C改編約定的匯 編代碼。 C++擴展了extern關鍵字,允許它用來指定它修飾的函數(shù)或全局變量 使用的是正規(guī)C約定。在C++術語中,稱這些函數(shù)或全局變量使用了C 鏈 接 。例如,為了聲明printf為C鏈接,需使用下面的原型:
extern ”C” int printf ( const char ?, ... );
這就告訴編譯器不要在這個函數(shù)上使用C++的名字改編規(guī)則,而使用C規(guī) 則來替代。但是,如果這樣做了,那么printf將不可以重載。這就提供了 一個簡易的方法用在C++和匯編程序接口上:使用C鏈接定義一個函數(shù), 然后再使用C調(diào)用約定。 為了方便,C++同樣允許定義函數(shù)或全局變量塊的C鏈接。通常函數(shù)或 全局變量塊用卷曲花括號表示。
extern ”C” {
/? C鏈接的全局變量和函數(shù)原型 ?/
}
如果你檢查了當今的C/C++編譯器中的ANSI C頭文件,你會發(fā)現(xiàn)在每 個頭文件上面都有下面這個東西:
#ifdef _ _cplusplus
extern ”C” {
#endif
而且在底部有一個包含閉卷曲花括號的同樣的結構。C++編譯器定義了 宏 cplusplus(有 兩條 領頭的下劃線)。上面的代碼片斷如果用C++來編 譯,那么整個頭文件就被一個extern "C"塊圍起來了,但是如果使用C來 編譯,就不會執(zhí)行任何操作(因為對于extern "C",C編譯器將產(chǎn)生一個 語法錯誤)。程序員可以使用同樣的技術用來在匯編程序中創(chuàng)建一個能 被C或C++使用的頭文件。
更多建議: