C++ での extern C の効果は何ですか?

06 C++ の関数名に C リンケージを持たせ (コンパイラは名前をマングルしません)、クライアント C コードが、関数の宣言だけを含む C 互換ヘッダー ファイルを使用して関数にリンク (使用) できるようにします。関数定義は、クライアント C リンカーが C 名を使用してリンクするバイナリ形式 (C++ コンパイラによってコンパイルされたもの) に含まれています。

C++ には関数名のオーバーロードがあり、C にはないため、C++ コンパイラは関数名をリンク先の一意の ID として使用することはできず、引数に関する情報を追加して名前をマングルします。 C では関数名をオーバーロードできないため、C コンパイラは名前をマングルする必要はありません。

29 を指定できます 個々の宣言/定義に明示的にリンクするか、ブロックを使用して一連の宣言/定義をグループ化し、特定のリンクを作成します:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

技術的なことが気になる場合は、C++03 標準のセクション 7.5 にリストされています。ここに簡単な要約があります (38 に重点を置いて) ):

  • 47 リンケージ仕様です
  • すべてのコンパイラが必須 「C」リンケージを提供する
  • リンケージ指定は名前空間スコープでのみ発生する
  • すべての関数型、関数名、変数名には言語リンケージがあります リチャードのコメントを参照してください: 外部リンケージを持つ関数名と変数名のみが言語リンケージを持ちます
  • 異なる言語リンケージを持つ 2 つの関数型は、それ以外は同一であっても、異なる型です
  • リンケージ スペックのネスト、内側のスペックが最終的なリンケージを決定します
  • 51 クラスメンバーに対しては無視されます
  • 特定の名前を持つ最大 1 つの関数は、"C" リンケージを持つことができます (名前空間に関係なく)
  • 60 関数に外部リンケージを強制します (静的にすることはできません) Richard のコメントを参照してください: 74 89 内 有効です。そのように宣言されたエンティティには内部リンケージがあるため、言語リンケージはありません
  • C++ から他の言語で定義されたオブジェクトへのリンク、および他の言語から C++ で定義されたオブジェクトへのリンクは、実装定義であり、言語に依存します。 2 つの言語実装のオブジェクト レイアウト戦略が十分に類似している場合にのみ、このような連携を実現できます

まだ投稿されていないので、少し情報を追加したかっただけです。

次のような C ヘッダーのコードをよく見かけます:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

これにより、マクロ「__cplusplus」が定義されるため、その C ヘッダー ファイルを C++ コードで使用できるようになります。ただし、できます マクロがNOTである従来のCコードで引き続き使用します 定義されているため、一意の C++ 構造は認識されません。

ただし、次のような C++ コードも見たことがあります。

extern "C" {
#include "legacy_C_header.h"
}

どちらが良いかはわかりませんが、両方を見てきました。


93 を逆コンパイルします 何が起こっているかを確認するために生成されたバイナリ

main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

生成された ELF 出力をコンパイルおよび逆アセンブルします。

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

出力には以下が含まれます:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
     9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
    10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

解釈

    <リ>

    104114 コードと同じ名前のシンボルに格納されていました

    <リ>

    他のシンボルは壊れていました。それらを分解しましょう:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

結論:次のシンボル タイプは両方とも そうではありません 壊れた:

  • 定義済み
  • 宣言されているが未定義 (126 )、別のオブジェクト ファイルからリンクまたは実行時に提供される

したがって、137 が必要になります。 呼び出し時の両方:

  • C++ からの C:tell 149 154 によって生成されたマングルされていないシンボルを期待する
  • C からの C++:tell 164 176 のマングルされていないシンボルを生成する 使用する

extern C で動作しないもの

名前マングリングを必要とする C++ 機能は 187 内では機能しないことが明らかになります。 :

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

C++ からの最小限の実行可能な C の例

完全を期すため、およびそこにある新機能については、C++ プロジェクトで C ソース ファイルを使用する方法も参照してください。

C++ から C を呼び出すのは非常に簡単です。各 C 関数には、マングルされていないシンボルが 1 つしかないため、追加の作業は必要ありません。

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

#include "c.h"

int f(void) { return 1; }

実行:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

196 なし リンクは次のエラーで失敗します:

main.cpp:6: undefined reference to `f()'

なぜなら 202 マングルされた 215 を見つけることを期待しています 、これ 229

GitHub の例。

C の例からの最小限の実行可能な C++

C から C++ を呼び出すのは少し難しく、公開したい各関数のマングルされていないバージョンを手動で作成する必要があります。

ここでは、C++ 関数のオーバーロードを C に公開する方法を示します。

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

実行:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

233 なし 次のエラーで失敗します:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

なぜなら 246 255 の壊れたシンボルを生成しました 見つかりません。

GitHub の例。

263 はどこですか C++ から C ヘッダーをインクルードする場合

  • 279 のような C ヘッダーの C++ バージョン 281 に依存している可能性があります https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html は次のように述べています。 C++ としてコンパイルします。」ですが、完全には確認していません。
  • 291 のような POSIX ヘッダー Do I need an extern "C" block to include standard POSIX C header? でカバーされていますか? 301経由 、Ubuntu 20.04で再現。 312 329 経由で含まれています .

Ubuntu 18.04 でテスト済み。