Translator: Qiu Longbin <robin.qiu(at)yeah.net>
FAQs in section [32]:
[32.1] 混合使用C和C++代碼時我需要知道什么?
下面是是一些要點(盡管一些編譯器廠商可能并不需要所有這些;請檢查你的編譯器廠商的文檔):
· 你必須使用你的C++編譯器編譯你的main()(例如,靜態(tài)初始化)
· 你應該用C++編譯器接管鏈接過程(例如,這樣可以獲得它的特別的庫)
· 你的C和C++編譯器可能需要來自于相同的廠商,并且是兼容的版本(這樣,它們才具備相同的調用約定)
另外,你應該需要繼續(xù)閱讀本文剩下的部分,找出如何才能在C++中調用C函數(shù)和/或如何在C中調用C++函數(shù)。
[32.2] 如何在C++代碼里include一個標準C頭文件?
#include 一個標準頭文件(比如<cstdio>),你通常不必作任何事。比如:
// 這是C++代碼
#include <cstdio> // #inlcude行沒有什么不尋常的
int main()
{
std::printf("Hello world\n"); // 調用也沒什么不尋常的
...
}
如果你認為std::printf()調用中std::部分不尋常,那么你最好“克服它(get over it)”。這句話的意思是使用標準庫中名字的標準方式,因此,你現(xiàn)在就應該習慣它。
然而,如果你正在使用C++編譯器編譯C代碼,你可能不想把所有的printf()的調用轉換成std::printf()。幸運的是,這種情況下C代碼可以使用舊風格的頭文件<stdio.h>而不是新風格頭文件<cstdio>和namespace的怪誕。
/* 這是C代碼,這里用C++編譯器編譯 */
#include <stdio.h> /* #inlcude行沒有什么不尋常的 */
int main()
{
printf("Hello world\n"); /* 調用也沒什么不尋常的 */
...
}
[32.3] 在我的C++代碼中如何include一個非系統(tǒng)的C頭文件?
如果你要包含的C頭文件不是由系統(tǒng)提供的,你可以把#include包裹在extern “C” { /* here */ }結構里。這就告訴C++編譯器在頭文件中聲明的函數(shù)是C函數(shù)。
// 這是C++代碼
extern "C" {
// 獲得聲明f(int i, char c, float x)
#include "my-C-code.h"
}
int main()
{
f(7, 'x', 3.14); // 注意:調用沒什么特別的
...
}
[32.4] 我要如何修改我自己的C頭文件,以使得在C++代碼中更容易地#inlcude它們?
如果你要include一個不是有系統(tǒng)提供的C頭文件,并且你可以修改這個C頭文件,你應該著重考慮在此頭文件中增加extern “C” {}邏輯,這就能使得對于C++用戶更容易地把它們包含進他們的C++代碼中去。因為C編譯器不理解extern “C”結構,你必須在#ifdef里面包裹extern “C” {和}行,讓它們被C編譯器忽略。
步驟#1:把下面三行放置在你的C頭文件的最頂處(注意:符號__cplusplus只在編譯器為C++編譯器時才被定義):
#ifdef __cplusplus
extern "C" {
#endif
步驟#2:把如下三行放置在你的C頭文件的最底部:
#ifdef __cplusplus
}
#endif
現(xiàn)在你可以在你的C++代碼里#includeC頭文件里而無需多余的extern “C”:
// 這是C++代碼
// 獲得聲明 f(int i, char c, float x)
#include "my-C-code.h" // 注意:#include 行沒什么特別的
int main()
{
f(7, 'x', 3.14); // 注意:調用沒什么特別的
...
}
[32.5] 我如何才能在我的C++代碼中調用一個非系統(tǒng)的C函數(shù)f(int, char, float)?
如果你有一個獨立的C函數(shù)想調用,并且,由于一些原因,你不能或不想#include那個聲明有此函數(shù)的頭文件,你可以在你的C++代碼里使用extern “C”語法聲明這個獨立的C函數(shù)。很自然,你需要使用函數(shù)的完全形式的原型:
extern "C" void f(int i, char c, float x);
多個C函數(shù)可以通過大括號被成組放在一個塊中。
extern "C" {
void f(int i, char c, float x);
int g(char* s, const char* s2);
double sqrtOfSumOfSquares(double a, double b);
}
這之后,你只需把它們當作C++函數(shù)簡單地調用他們就行了:
int main()
{
f(7, 'x', 3.14); // 注意:調用沒什么特別的
...
}
[32.6] 如何創(chuàng)建一個C++函數(shù)f(int, char, float),它可以被我的C代碼調用?
// 這是C++代碼
// 使用 extern "C"聲明 f(int,char,float) :
extern "C" void f(int i, char c, float x);
...
// 在某個C++模塊中定義 f(int,char,float):
void f(int i, char c, float x)
{
...
}
extern “C”行告知編譯器傳送給鏈接器的外部信息(external information)使用C調用約定和名字重整(name mangling)規(guī)則(比如,加一個前綴下劃線)。因為名字重載(name overloading)不被C支持,所以你不能同時寫幾個被C程序調用的重載函數(shù)。
[32.7] C/C++函數(shù)被C++/C函數(shù)調用時,為何鏈接器給出錯誤信息?
如果你未能正確處理extern “C”,你將得到鏈接錯誤而不是編譯錯誤。這歸因于這樣一個事實:C++編譯器在函數(shù)名“重整(mangle)方面與C編譯器常常不同。”
請查看前面兩個關于如何使用extern “C”的FAQ.
[32.8] 如何能夠傳遞一個C++類對象到/從一個C函數(shù)?
這里是一個例子(extern “C”的信息,參見前面兩個FAQs)。
Fred.h:
/* 這個頭文件可以被C和C++編譯器讀取 */
#ifndef FRED_H
#define FRED_H
#ifdef __cplusplus
class Fred {
public:
Fred();
void wilma(int);
private:
int a_;
};
#else
typedef
struct Fred
Fred;
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__STDC__) || defined(__cplusplus)
extern void c_function(Fred*); /* ANSI C prototypes */
extern Fred* cplusplus_callback_function(Fred*);
#else
extern void c_function(); /* K&R style */
extern Fred* cplusplus_callback_function();
#endif
#ifdef __cplusplus
}
#endif
#endif /*FRED_H*/
Fred.cpp:
// 這是C++代碼
#include "Fred.h"
Fred::Fred() : a_(0) { }
void Fred::wilma(int a) { }
Fred* cplusplus_callback_function(Fred* fred)
{
fred->wilma(123);
return fred;
}
main.cpp:
// 這是C++代碼
#include "Fred.h"
int main()
{
Fred fred;
c_function(&fred);
...
}
c-function.c:
/* 這是C代碼 */
#include "Fred.h"
void c_function(Fred* fred)
{
cplusplus_callback_function(fred);
}
不同于你的C++代碼,除非指針是完全一致的類型,否則你的C代碼不能判斷出兩個指針指向同一個對象。比如,在C++中,很容易檢查一個Derived*的指針dp同Base*的指針bp一樣指向了相同的對象:只要說if (dp == bp ) ...C++編譯器自動把兩個指針轉換到相同的類型,在這個例子中,轉換到Base*,然后再比較它們。依賴于C++編譯器的實現(xiàn)細節(jié),這個轉換時常改變了指針值的位(bits)信息。然而,你的C編譯器將不知道如何做那樣的轉換,因此,比如從Derived*到Base*的轉換必須發(fā)生在用C++編譯器編譯的代碼中,而不應該在用C編譯的代碼中。
注意:當把兩者轉換成void*時你必須特別小心,因為這種轉換將不允許C/C ++編譯器做適當?shù)闹羔樥{整!例如(接著前面的章節(jié)),你把dp和bp都賦值給void*指針dpv和bpv,你可能獲得dpv != bpv的情況,即使dp == bp。同時你會收到警告信息。
[32.9] 我的C函數(shù)可以直接訪問C++類對象中的數(shù)據(jù)嗎?(譯者:符合“POD”類型,就可以了)
有時可以。
(傳遞一個C++類對象到/從一個C函數(shù)的基本信息,參見前面的FAQ)
你可以安全地從C函數(shù)中訪問C++對象的數(shù)據(jù),如果C++類滿足下面條件:
· 沒有virtual函數(shù)(包括繼承來的virtual函數(shù))
· 所有數(shù)據(jù)都有處在同一個訪問級段中(private/protected/public)
· 沒有完全包含(fully-contained,譯注:成員對象,即“包含”而非“聚合”)的帶有virtual函數(shù)的子對象(subobjects)
如果C++類擁有很多基類(或者如果任一完全包含的子對象有多個基類),技術上而言訪問其數(shù)據(jù)是不可移植的,因為繼承之下的class布局(layout)上,語言并為強制。但是在實際上,所有C++編譯器都幾乎以同樣的方式處理:基類對象第一個出現(xiàn)(多繼承時按從左到右的次序),然后是成員對象。
更進一步,如果類(或者任意基類)含有一些virtual函數(shù),幾乎所有的C++編譯器都放置一個void*在第一個virtual函數(shù)出現(xiàn)的位置或者在對象的最前面。還有就是,這些都不是語言要求的,但它是“每個編譯器”都采取的方式。
如果類有virtual基類,這就更加復雜和移植性更差。通用的實現(xiàn)技術是讓對象包含一個virtual基類對象(V)(無論V作為virtual基類出現(xiàn)在繼承層次結構的什么地方)。對象的其余部分以通常的次序出現(xiàn)。每個派生的含有V作為virtual基類的對象,實際上擁有一個指針,指向最終對象的V部分。(譯者:更多這方面的信息參見《Inside C++ Object Model》)
[32.10] 為什么我感覺到相對于C,在C++中我“更遠離機器層面”?
因為你的確如此。
作為一個OO編成語言,C++允許你對問題域本身建模,這就允許你在問題域語言中編程而不是在解空間(solution domain)編程。
C的一個強大之處在于它“沒有隱藏什么機制(no hidden mechanism)”:你所見,即你所得。你可以讀你的C程序并且“查看(see)”每個時鐘周期。C++中就不是這么回事了;
一些老頑固的C程序員(比如我們中的一些曾)常常對這個特征很矛盾(你也可以說是“敵對”)。但是當他們完成了到OO思想的轉變,他們就能時常體會到盡管C++對程序員隱藏了一些機制,它也提供了一個抽象層和表達上的經濟(economy of expression)(譯注:從成本上),這降低了維護成本又不會降低運行時性能。
C++并沒有試圖讓差的程序員能夠避免寫出差的程序;它允許明理(reasonable)的開發(fā)者寫出出眾的軟件。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=624241