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