|
Mixing C and C++ Code in the Same Program By Stephen Clamage, Sun Microsystems, Sun ONE Studio Solaris Tools Development Engineering Translator: Qiu Longbin <robin.qiu(at)yeah.net> C++語(yǔ)言提供了一個(gè)混合代碼的機(jī)制,使得代碼可以在同一個(gè)程序中被兼容的C和C++編譯器編譯。在你移植代碼到不同的平臺(tái)和編譯器時(shí),你會(huì)體驗(yàn)到不同的成功度。本文展示了當(dāng)你混合使用C,C++時(shí),如何解決出現(xiàn)的一般的問(wèn)題。文中所有情況,展示了使用Sun C和C++編譯器時(shí)所要做的事情。(譯注:GCC的gcc和g++也是這一對(duì)組合。) 內(nèi)容
使用兼容的編譯器
Sun C和C++編譯器遵循Solaris OS ABI并且是兼容的。第三方的Solaris OS C編譯器也必須遵循ABI。任何與Solaris Os兼容的C編譯器也同樣與Sun C++編譯器兼容。 被你的C編譯器使用的C運(yùn)行時(shí)庫(kù)也必須同C++編譯器兼容。C++包括了標(biāo)準(zhǔn)C運(yùn)行時(shí)庫(kù)作為其子集,只有稍許不同。如果C++編譯器提供了自己版本的C頭文件,那么這些頭文件的版本被C使用時(shí)也必須是兼容的。
Sun C和C++編譯器使用兼容的頭文件,并且使用同樣的C運(yùn)行時(shí)庫(kù)。他們是完全兼容的。 在C++源代碼中訪問(wèn)C代碼
C++語(yǔ)言提供了一個(gè)“鏈接規(guī)范(linkage specification)”,用它你可以聲明函數(shù)或?qū)ο笞裱囟ㄕZ(yǔ)言的程序鏈接約定。對(duì)象和函數(shù)的默認(rèn)鏈接是C++的。所有C++編譯器也為兼容的C編譯器提供了C鏈接。 當(dāng)你需要訪問(wèn)一個(gè)用C鏈接編譯的函數(shù)(例如,某個(gè)函數(shù)被C編譯器編譯),就要聲明那個(gè)函數(shù)具備C鏈接(譯注:在C++代碼中)。即使大多數(shù)C++編譯器對(duì)C和C++數(shù)據(jù)對(duì)象的鏈接沒(méi)有什么不同,你也需要在你的C++代碼中聲明C數(shù)據(jù)對(duì)象(data objects)具有C鏈接。類(lèi)型(types)沒(méi)有C或C++鏈接,除了指向函數(shù)的指針(pointer-to-function)類(lèi)型。 聲明鏈接規(guī)范 使用下述標(biāo)記之一來(lái)聲明一個(gè)對(duì)象或函數(shù)具備某種語(yǔ)言language_name的鏈接。
第一個(gè)標(biāo)記指定了緊隨其后的聲明(或定義)具有語(yǔ)言language_name的鏈接約定。第二個(gè)標(biāo)記指定花括號(hào)內(nèi)的所有都具有language_name的鏈接。注意,第二個(gè)標(biāo)記的最后花括號(hào)后面不要有分號(hào)。
你可以嵌套鏈接規(guī)范,他們沒(méi)有創(chuàng)建一個(gè)范圍域(scope)。考慮下面的例子:
在C++代碼中包含C頭文件
如果你想使得頭文件同時(shí)適合于C和C++編譯器,你可能把所有聲明都放置在了extern “C”花括號(hào)中,但是C編譯器并不認(rèn)識(shí)這些語(yǔ)法。每個(gè)C++編譯器都預(yù)定義了宏__cplusplus,這樣你就可以使用這個(gè)宏來(lái)防衛(wèi)C++語(yǔ)法擴(kuò)展:
假定你想在你的C++代碼中更容易地使用C庫(kù)。并且假定你不使用C風(fēng)格的訪問(wèn)方式,你可能想增加成員函數(shù),或許虛函數(shù),也可能從class派生等等。你如何完成這個(gè)變換并確保C庫(kù)函數(shù)仍然能識(shí)別你的struct?考慮下面這個(gè)例子中C的struct buf的使用:
你想把這個(gè)struct轉(zhuǎn)變進(jìn)C++ class,并做下述改變,使它更容易使用:
class mybuf的接口看來(lái)更象C++代碼,并且更容易整合進(jìn)面向?qū)ο箫L(fēng)格的編程 ─ 如果它可行的話。 當(dāng)成員函數(shù)傳遞this指針給buf函數(shù)時(shí)發(fā)生了什么?C++類(lèi)布局(layout)匹配C布局嗎?this指針指向數(shù)據(jù)成員,還是指向buf?如果增加虛函數(shù)到mybuf又會(huì)如何呢?
C++標(biāo)準(zhǔn)對(duì)buf和class mybuf的兼容性沒(méi)有任何保證。這里的代碼,沒(méi)有虛函數(shù),可以工作,但你不能指望這個(gè)。如果你增加了虛函數(shù),這個(gè)代碼會(huì)失敗,因?yàn)榫幾g器增加了額外的數(shù)據(jù)(比如指向虛表的指針)放在class的開(kāi)始處。 可移植的方案是把struct buf單獨(dú)放著不動(dòng)它,盡管你想保護(hù)數(shù)據(jù)成員并僅僅通過(guò)成員函數(shù)提供訪問(wèn)。僅當(dāng)你不改變聲明的情況下,你才能保證C和C++的兼容性。 你可以從C struct buf派生出一個(gè)C++ class mybuf,并且傳遞指向基類(lèi)buf的指針給mybuf的成員函數(shù)。當(dāng)轉(zhuǎn)換mybuf* 到 buf*時(shí),如果指向mybuf的指針沒(méi)有指向buf數(shù)據(jù)的起始處,C++編譯器會(huì)自動(dòng)調(diào)整它。mybuf的布局在C++編譯時(shí)可能發(fā)生改變,但是操縱mybuf和buf對(duì)象的C++源代碼將到處都可以工作。下面的例子展示了一個(gè)可移植方法給C struct增加C++和面向?qū)ο筇卣鳎?/font>
C++代碼可以自由的創(chuàng)建和使用mybuf對(duì)象,傳遞它們到那些期望buf對(duì)象的C代碼,并且可以結(jié)合地很好。當(dāng)然如果你為mybuf增加了數(shù)據(jù)成員,C代碼就不知道它們。那就是一般類(lèi)設(shè)計(jì)的考慮。你要當(dāng)心要一致性地創(chuàng)建和刪除(delete)buf和mybuf對(duì)象.讓C代碼刪除(free)一個(gè)有C代碼創(chuàng)建的對(duì)象是最安全的,并且不允許C代碼刪除一個(gè)mybuf對(duì)象。
在C源代碼中訪問(wèn)C++代碼:
如果你聲明一個(gè)C++函數(shù)具有C鏈接,它就可以在由C編譯器編譯的函數(shù)中被調(diào)用。一個(gè)聲明具有C鏈接的函數(shù)可以使用所有C++的特征,如果你想在C代碼中訪問(wèn)它,它的參數(shù)和返回值必須是在C中可訪問(wèn)的。例如,如果一個(gè)函數(shù)聲明有一個(gè)IOstream類(lèi)的引用作為參數(shù),就沒(méi)有(可移植的)方法來(lái)解析這個(gè)參數(shù)類(lèi)型給C編譯器。C語(yǔ)言沒(méi)有引用,模板,或具備C++特征的class. 這里是一個(gè)具備C鏈接的C++函數(shù)的例子:
你可以在頭文件中聲明一個(gè)函數(shù)print,被C和C++代碼共用:
你可以至多聲明重載集中的一個(gè)函數(shù)作為extern “C”,因?yàn)橐粋€(gè)C函數(shù)僅僅可以有一個(gè)給定的名字。如果你要在C中訪問(wèn)重載函數(shù),你可以以不同的名字寫(xiě)出C++ wrapper函數(shù),見(jiàn)下面的例子:
這里是C頭文件wrapper函數(shù)的例子:
你也需要包裹(wrapper)函數(shù)來(lái)調(diào)用template functions,因?yàn)?/font>template functions不能聲明為extern “C”:
C++代碼仍然可以訪問(wèn)重載函數(shù)和template functions。C代碼必須使用wrapper functions。 在C中訪問(wèn)C++ class
假定你有一個(gè)C++ class如下:
你不能在C代碼中聲明class M。你能做的最好的事就是傳遞指向class M 對(duì)象的指針,這類(lèi)似于在C標(biāo)準(zhǔn)I/O中傳遞FILE對(duì)象。你可以在C++中寫(xiě)extern “C”函數(shù)訪問(wèn)class M 對(duì)象并在C代碼中調(diào)用這些函數(shù)。下面是一個(gè)C++函數(shù),被設(shè)計(jì)來(lái)調(diào)用成員函數(shù)foo:
下面是C代碼的一個(gè)例子,它使用了class M:
混合IOstream和C標(biāo)準(zhǔn)I/O
你可以在C++程序中使用來(lái)自于標(biāo)準(zhǔn)C頭文件<stdio.h>的C標(biāo)準(zhǔn)I/O,因?yàn)?/font>C標(biāo)準(zhǔn)I/O是C++的一部分。 任何關(guān)于在同一個(gè)程序中混合IOstream和標(biāo)準(zhǔn)I/O的考慮都不依賴(lài)于程序是否以明確地包含C代碼。這個(gè)問(wèn)題對(duì)于純粹的C++程序使用標(biāo)準(zhǔn)I/O和IOstream是一樣的。
Sun C和C++使用同樣的C運(yùn)行時(shí)庫(kù),這在關(guān)于兼容的編譯器小節(jié)中注明過(guò)了。使用Sun編譯器,你可以在同一個(gè)程序中自由地在C和C++代碼中使用標(biāo)準(zhǔn)I/O。 C++標(biāo)準(zhǔn)說(shuō),你可以在同一個(gè)目標(biāo)“流(stream)”上混合使用標(biāo)準(zhǔn)I/O函數(shù)和IOstream函數(shù),比如標(biāo)準(zhǔn)輸入和輸出流。但是C++實(shí)現(xiàn)在它們的遵從度上可能不同。一些系統(tǒng)要求你在做任何I/O之前先顯式地調(diào)用sync_with_stdio()函數(shù)。在同一個(gè)流或者文件上混合I/O風(fēng)格,實(shí)現(xiàn)品在I/O的效能方面也不同。最差情況下,你得到為每個(gè)字符的輸入輸出做一個(gè)系統(tǒng)調(diào)用的結(jié)果。如果程序有大量的I/O,性能可能是不可接受的。
最安全的途徑是對(duì)任一給定的文件/標(biāo)準(zhǔn)流,堅(jiān)持使用標(biāo)準(zhǔn)I/O或IOstream風(fēng)格。在一個(gè)文件或流上使用標(biāo)準(zhǔn)I/O,在另一個(gè)不同的一個(gè)文件或流上使用IOstream,不會(huì)導(dǎo)致任何問(wèn)題。
使用指向函數(shù)的指針
指向函數(shù)的指針必須指明是否指向一個(gè)C函數(shù)或C++函數(shù),因?yàn)?/font>C和C++函數(shù)可能采用不同的調(diào)用約定。否則,編譯器不知道究竟要產(chǎn)生哪種函數(shù)調(diào)用的代碼。多數(shù)系統(tǒng)對(duì)C和C++并沒(méi)有不同的調(diào)用約定,但是C++允許存在這種可能性。因此你必須在聲明指向函數(shù)的指針時(shí)要小心,確保類(lèi)型匹配??紤]下面的例子:
Line 1聲明了pfun指向一個(gè)C++函數(shù),因?yàn)樗鄙冁溄诱f(shuō)明符。 要確保指向函數(shù)的指針的鏈接規(guī)范與它將要指向的函數(shù)匹配。在下面這個(gè)正確的例子中,所有聲明都包含在extern “C”花括號(hào)中,確保了類(lèi)型匹配。
指向函數(shù)指針有另外一個(gè)微妙之處,它可能給程序員帶來(lái)陷阱。鏈接規(guī)范應(yīng)用于函數(shù)所有的參數(shù)類(lèi)型和返回類(lèi)型上。如果你將一個(gè)詳細(xì)聲明的指向函數(shù)的指針(pointer-to-function)用作函數(shù)參數(shù),鏈接規(guī)范也同樣地作用在了這個(gè)指向函數(shù)的指針上。如果你通過(guò)使用typedef聲明了一個(gè)指向函數(shù)的指針,這個(gè)typedef類(lèi)型在用于函數(shù)聲明中時(shí),鏈接規(guī)范不會(huì)受到影響。例如,考慮下面的代碼:
前兩行可以出現(xiàn)在一個(gè)程序文件中,第三行可以出現(xiàn)在一個(gè)頭文件中,在此頭文件中你不想暴露出內(nèi)部使用的typedef的名字。盡管你想讓foo的聲明和定義相匹配,但它們不匹配。foo的定義接受一個(gè)指向C++函數(shù)的指針,但是其聲明接受一個(gè)指向C函數(shù)的指針。這段代碼聲明了一對(duì)重載函數(shù)。(譯注:在此是參數(shù)類(lèi)型的鏈接規(guī)范不同。)
為了避免這個(gè)問(wèn)題,得在聲明中一致地使用typedefs,或者以某個(gè)適當(dāng)?shù)逆溄右?guī)范包圍typedefs。例如,假定你想讓foo接受一個(gè)指向C函數(shù)的指針,你可以以下面的方式寫(xiě)出foo的定義:
使用C++異常
傳播(Propagating)異常 從C函數(shù)中調(diào)用C++函數(shù),并且C++函數(shù)拋出了一個(gè)異常,將會(huì)發(fā)生什么?在是否會(huì)使得該異常有適當(dāng)?shù)男袨檫@個(gè)問(wèn)題上C++標(biāo)準(zhǔn)有些含糊,并且在一些系統(tǒng)上你不得不采取特別的預(yù)防措施。一般而言,你必須得求諸用戶(hù)手冊(cè)來(lái)確定代碼是否以適當(dāng)?shù)姆绞焦ぷ鳌?/font> Sun C++中不需要預(yù)防措施。Sun C++中的異常機(jī)制不影響函數(shù)調(diào)用的方式。當(dāng)C++異常被拋出時(shí),如果一個(gè)C函數(shù)正處于活動(dòng)狀態(tài),C函數(shù)將轉(zhuǎn)交給異常處理過(guò)程。 混合異常和set_jmp,long_jmp 許多C++專(zhuān)家相信long_jmp不應(yīng)該與異常整合,因?yàn)楹芾щy準(zhǔn)確地指定它該如何作為。
如果你在混合有C++的C代碼中使用long_jmp,要確保long_jmp不要跨越(cross over)活動(dòng)的C++函數(shù)。如果你不能確保這點(diǎn),查看一下是否你可以通過(guò)禁用異常來(lái)編譯那個(gè)C++代碼。如果對(duì)象的析構(gòu)器被繞過(guò)了,你仍舊可能有問(wèn)題。 鏈接程序
某時(shí),多數(shù)C++編譯器要求main函數(shù)要被C++編譯。這個(gè)要求今天來(lái)說(shuō)并不常見(jiàn),Sun C++就不要求這點(diǎn)。如果你的C++編譯器需要編譯main函數(shù),但你由于某種原因不能這么做,你可以改變C main函數(shù)的名字并從一個(gè)C++ main的包裹函數(shù)中調(diào)用它。例如,改變C main函數(shù)的名字為C_main,并寫(xiě)如下C++代碼:
當(dāng)然,C_main必須是被聲明在C代碼中,并返回一個(gè)int。如上注解,使用Sun C++是不會(huì)有這個(gè)麻煩。 即使你的程序主要由C代碼構(gòu)成,但使用了C++庫(kù),你需要鏈接C++運(yùn)行時(shí)以支持與C++編譯器提供的庫(kù)一起編譯進(jìn)程序。做這件事的最簡(jiǎn)單和最好的方式是使用C++編譯器驅(qū)動(dòng)鏈接過(guò)程。C++編譯器的驅(qū)動(dòng)器知道要鏈接什么庫(kù),次序如何。特定的庫(kù)可以依賴(lài)編譯C++代碼時(shí)使用的選項(xiàng)。
假定你有C程序文件main.o, f1.o和f2.o,你可以使用C++程序庫(kù)helper.a。用Sun C++,你要如下引發(fā)命令行:
必要的C++運(yùn)行時(shí)庫(kù),如libCrun和libCstd被自動(dòng)地鏈接進(jìn)去。helper.a可能要求使用額外的鏈接選項(xiàng)。如果你由于某種原因不能使用C++編譯器,你可以使用CC命令的dryrun選項(xiàng)來(lái)獲得編譯器引發(fā)出的命令列表,并把它們捕獲進(jìn)一個(gè)shell腳本。因?yàn)榇_切的命令(復(fù)數(shù))依賴(lài)于命令行選項(xiàng),你應(yīng)該復(fù)查—dryrun選項(xiàng)輸出和命令行的任何一個(gè)變動(dòng)。
關(guān)于作者
Steve Clamage從1994年在Sun至今?。它當(dāng)前是C++編譯器和Sun ONE Studio編譯器套件的技術(shù)領(lǐng)導(dǎo)。它從1995年開(kāi)始是ANSI C++委員會(huì)的主席。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=626342 |
|
|