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