2009-09-04 89 views
25

我寫了一個C++函數,我需要從C程序調用。爲了使它可以從C中調用,我在功能聲明上指定了extern "C"。然後我編譯了C++代碼,但編譯器(Dignus Systems/C++)爲該函數生成了一個mangled name。所以,它顯然沒有兌現extern "C"僅在函數聲明中需要extern「C」嗎?

要解決此問題,我將extern "C"添加到函數定義。在此之後,編譯器生成可從C中調用的函數名稱。從技術上講,extern "C"只需要在函數聲明中指定。這是正確的嗎? (C++ FAQ Lite就是一個很好的例子。)你是否也應該在函數定義中指定它?

下面就來演示這樣的例子:

/* ---------- */ 
/* "foo.h" */ 
/* ---------- */ 

#ifdef __cplusplus 
extern "C" { 
#endif 

/* Function declaration */ 
void foo(int); 

#ifdef __cplusplus 
} 
#endif 

/* ---------- */ 
/* "foo.cpp" */ 
/* ---------- */ 

#include "foo.h" 

/* Function definition */ 
extern "C"    // <---- Is this needed? 
void foo(int i) { 
    // do something... 
} 

我的問題可能是不正確編碼的東西的結果,或者我可能已經找到了編譯器錯誤。無論如何,我想諮詢一下stackoverflow,以確保我知道哪個技術上是「正確」的方式。

+4

如果您在示例代碼中顯示foo.c中的外部「C」,您確定實際上獲得了foo的篡改嗎?或者這只是其他更復雜的代碼中發生的事情?我經常將這個問題看作是忘記在foo.c中包含foo.h的一個症狀。 – 2009-09-04 19:01:26

+0

@布魯克斯摩西:這是一個很好的觀點。在我的實際代碼中,比這個「foo」示例稍微複雜一點,我在「cpp」源文件中包含頭文件。 是什麼讓我覺得編譯器改變名字是這樣的:如果我沒有在函數定義中包含'extern「C」',那麼編譯器列表顯示外部符號「foo_FPFPCc_v」。當包含「extern」C「'時,列表顯示外部符號」foo「。 – bporter 2009-09-04 19:41:58

+1

@bporter - 嘗試使用簡化示例的編譯器可能會很有趣。如果它顯示相同的行爲,那麼您可能需要向供應商發送便條。如果它沒有顯示相同的內容,那麼你應該跟蹤你真正的構建中發生了什麼,因爲這會表明錯誤的頭部被引入(或者其他的東西使得函數聲明被遺漏)。 – 2009-09-04 19:53:24

回答

29

只要聲明具有它並且已經在定義的編譯中看到,就不應該在函數定義上需要'extern "C"'。該標準具體規定了(7.5/5聯動規格):

可以在看到明確的聯動規格後聲明沒有聯動規格的功能;前面聲明中明確指定的鏈接不受此類函數聲明的影響。

不過,我一般不把「extern "C"」上把定義爲好,因爲它實際上是在與外部的「C」鍵的功能。很多人討厭不必要的時候,多餘的東西在聲明上(比如把virtual放在方法覆蓋上),但我不是其中之一。

+0

@Michael Burr - 感謝您的回覆。聽起來像'extern'C''在定義中是不需要的,只要它是在前面的聲明中指定的。就像你提到的那樣,我可以繼續在兩個地方指定它。 (事實上​​,除非我發現我的問題不是編譯器錯誤,否則我可能需要,如果它看起來像編譯器錯誤,那麼我會將它報告給供應商,以便他們可以查看它。) – bporter 2009-09-04 20:23:27

+0

我認爲我是唯一一個在方法上覆蓋'virtual'的人:-) – Mawg 2017-03-16 08:38:41

0

它應該在兩者之間。編譯器需要知道在編譯調用站點(它可能只看到一個聲明)時使用C符號名稱和調用約定,並且編譯器還需要知道生成C符號名稱並在編譯時使用C調用約定函數定義本身(可能看不到任何其他聲明)。

現在,如果您有一個可從定義存在的翻譯單元中看到的extern-C聲明,那麼您可能能夠從定義中離開extern-C,但我沒有當然知道。

+3

在所描述的情況下這是不正確的,其中foo.h包含在foo.c中,並且「脫離」會誤導使用標準所需的行爲。如果在foo.c中包含foo.h - 正如人們總是應該做的那樣,以便編譯器可以檢查該聲明實際上是否準確! - 那麼就沒有必要(除了讀者可能更清楚) - 將外部「C」放在foo.c中的定義上。 – 2009-09-04 18:58:03

1

編輯:
好像我誤解了這個問題。 不管怎麼說,我想:


// foo.cpp 
/* Function definition */ 

#include "foo.h" 

void foo(int i) { 
//do stuff 
} 
void test(int i) 
{ 
// do stuff 
} 

// foo.h 
#ifdef __cplusplus 
extern "C" { 
#endif 

/* Function declaration */ 
void foo(int); 

#ifdef __cplusplus 
} 
#endif 

void test(int); 

使用命令nm查看從編譯的文件中的符號:


linuxuser$ nm foo.o 
00000006 T _Z4testi 
     U __gxx_personality_v0 
00000000 T foo 

這清楚地表明,函數聲明爲extern「C」這個名字是不是錯位和定義時不需要外部「C」關鍵字。
如果需要每個C庫代碼都不用extern,那麼C將在C++程序中無法使用。

+0

@Neeraj - 感謝你的例子。這也是我期望在我的案例中看到的。看來我的編譯器會損壞「foo」函數名稱,除非我在聲明和定義中指定了'extern「C」'。我認爲你和其他一些海報是正確的,只要在先前的聲明中指定了'extern'C''就不需要定義。 – bporter 2009-09-04 20:10:44

0

圍繞定義的extern "C"不是必需的。你可以把它放在宣言中。一個音符在你的榜樣......

#ifdef __cplusplus 
extern "C" { 
#endif 

/* Function declaration */ 
void foo(int); 

#ifdef __cplusplus 
} 
#endif 

你的代碼是尋找預處理器宏「__cplusplus」。

雖然它通常實現,但根據您的編譯器,這可能會或可能不會被定義。在你的例子中,你也在聲明周圍使用extern "C",但是你在不是檢查「__cplusplus」宏,這就是爲什麼我懷疑它一旦你這樣做的原因。

參見下面的評論 - 標準C++需要由預處理器中定義的宏__cplusplus

+0

@whitej - 「__cplusplus」宏恰好由我的特定編譯器定義,但你說得對,它不是「標準」。另外,我在頭文件中使用這個宏,因爲這是我聲明函數的地方(並且這是大多數人希望瞭解該接口的地方),它允許我在C和C++文件中包含頭文件。 C編譯器不支持「extern」C「',所以這個宏只在適當的時候插入'extern」C「'。請注意,如果指定了「extern」C「',則在」foo.cpp「中不需要該宏,因爲」foo.cpp「將始終由C++編譯器編譯。 – bporter 2009-09-04 20:59:42

+4

編譯C++模塊時需要定義標準所需的「'__cplusplus'」宏 - 使用它絕對沒有問題。 – 2009-09-04 22:45:49

+1

儘管頭文件中經常需要「__cplusplus」(因爲它們可能包含在C或C++模塊中),但它通常不需要在.c或.cpp文件中,因爲它們通常被設計爲編譯爲C或C++ ,但不是兩者都有(當然也有例外,但通常不會)。 – 2009-09-04 22:48:58

1

剛剛遇到這種情況......不愉快的經歷。

以下是以我c文件中的一個聲明:

void unused_isr(void) {} 
void ADC_IRQHandler(void)  __attribute__ ((weak, alias("unused_isr"))); 

接下來,在cpp文件中,我定義的地方:

void ADC_IRQHandler(void) {                     
    ... 
} 

我忘了向前聲明更改爲:

void ADC_IRQHandler(void); 

我花了一段時間纔想出來我在AD轉換方面做得很好,但是我沒有給定義添加「extern C」!

extern "C" void ADC_IRQHandler(void) {                     
    ... 
} 

只是我的兩分錢爲什麼它在某些情況下可能有用,有習慣將它添加到定義中。

1

我想這裏需要澄清一下,因爲我剛剛有一個類似的問題,並花了一段時間才弄清楚我的腦海中,只有布魯克斯摩西觸及這一點,我認爲這需要說明更清楚地...

總之,頭文件可能會讓你失望,所有編譯器看到的都是cpp文件,如果頭文件沒有包含在你的cpp(我通常會看到)的extern「C」中,那麼extern「C」將需要位於cpp文件的某處(無論是在定義中還是其他聲明中),因此CXX編譯器可以知道使用C鏈接進行編譯,編譯器不關心頭部,只有鏈接器。