web-dev-qa-db-ja.com

extern "C"は関数宣言でのみ必要ですか?

Cプログラムから呼び出す必要のあるC++関数を作成しました。 Cから呼び出し可能にするために、関数declarationextern "C"を指定しました。次に、C++コードをコンパイルしましたが、コンパイラー(Dignus Systems/C++)は、関数の マングル名 を生成しました。したがって、それは明らかにextern "C"を尊重していませんでした。

これを解決するために、関数definitionextern "C"を追加しました。この後、コンパイラーはCから呼び出し可能な関数名を生成しました。

技術的には、extern "C"は関数宣言でのみ指定する必要があります。これは正しいですか? ( C++ FAQ には、この良い例があります。)関数定義でも指定する必要がありますか?

これを示す例を次に示します。

/* ---------- */
/* "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を調べて、どちらが技術的に「正しい」方法であるかを確認したかったのです。

34
bporter

'extern "C" 'は、宣言にそれがあり、定義のコンパイルですでに見られている限り、関数定義で必要とされるべきではありません。規格は具体的に述べています(7.5/5リンケージ仕様):

明示的なリンケージ仕様が確認された後、リンケージ仕様なしで関数を宣言できます。以前の宣言で明示的に指定されたリンケージは、そのような関数宣言の影響を受けません。

しかし、私は一般的に 'extern "C" 'も定義にあります。これは、実際には外部「C」リンケージを持つ関数だからです。多くの人は、宣言に不要で冗長なものがあると嫌いになります(メソッドのオーバーライドにvirtualを置くなど)が、私はその1人ではありません。

38
Michael Burr

同様の問題があり、頭の中でこれを明確にするのに時間がかかったので、これをここで明確にする必要があると思います。ブルックスモーセだけがこれに適切に触れました。もっと明確に述べる必要があると思います。 。

要約すると、ヘッダーはあなたを捨てる可能性があり、コンパイラーが認識するのはcppファイルだけであり、ヘッダーがextern "C"に含まれていない場合(私がよく見る)、extern "C"は次のことを行う必要があります。 CXXコンパイラがCリンケージでそれを作成することを認識できるように、cppファイルのどこかに(定義または別の宣言のいずれかで)存在します。コンパイラはヘッダーを気にせず、リンカだけを気にします。

1
othane

ちょうどこの状況に遭遇しました...楽しい経験ではありません。

以下は私のcファイルの1つで宣言されました:

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

次に、私が定義したcppファイルのどこかにあります。

void ADC_IRQHandler(void) {                                                                                  
    ...
}

そして、前方宣言を次のように変更するのを忘れました。

void ADC_IRQHandler(void);

AD変換に関してすべてが正しく行われていることがわかるまでに少し時間がかかりましたが、定義に「externC」を追加できませんでした。

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

特定の状況でそれを定義に追加する習慣があると便利なのは、私の2セントだけです。

1
Anne van Rossum

定義の周りのextern "C"は必要ありません。宣言の周りに置くだけで逃げることができます。あなたの例の1つのメモ...

#ifdef __cplusplus
extern "C" {
#endif

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

#ifdef __cplusplus
}
#endif

あなたのコードはプリプロセッサマクロ「__cplusplus」を探しています。

これは一般的に実装されていますが、コンパイラによっては、定義されている場合と定義されていない場合があります。あなたの例では、宣言の周りにもextern "C"を使用していますが、そこではそうではありません "__cplusplus"マクロをチェックしているので、一度実行すると機能したと思います。 。

以下のコメントを参照してください—標準C++では、__cplusplusマクロをプリプロセッサで定義する必要があります。

1
whitej

編集:
質問を誤解していたようです。とにかく、私は試しました:


// 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"として宣言された関数の名前がマングルされておらず、extern "C"キーワードが定義に必要ないことを明確に示しています。
extern "C"なしで記述されたすべてのCライブラリコードが必要だったとしたら、C++プログラムでは使用できなかったでしょう。

1
sud03r

それは両方の周りにあるはずです。コンパイラは、呼び出しサイト(宣言のみが表示される場合があります)をコンパイルするときにCシンボル名と呼び出し規約を使用することを知っている必要があります。また、コンパイラは、Cシンボル名を生成し、コンパイル時にC呼び出し規約を使用することも知っている必要があります。関数定義自体(他の宣言が表示されない場合があります)。

さて、定義が存在する翻訳ユニットから見えるextern-C宣言がある場合、定義からextern-Cを除外することで回避できるかもしれませんが、それは確かではありません。 。

0
Tyler McHenry