C ++でextern“ C” {#include <foo.h>}が必要なのはなぜですか?


136

なぜ使用する必要があるのですか?

extern "C" {
#include <foo.h>
}

具体的には:

  • いつ使用すべきですか?

  • それを使用する必要があるコンパイラ/リンカーレベルで何が起こっていますか?

  • コンパイル/リンクに関して、これを使用する必要がある問題をどのように解決しますか?

回答:


122

CとC ++は表面的には似ていますが、それぞれが非常に異なるコードセットにコンパイルされます。C ++コンパイラにヘッダーファイルを含める場合、コンパイラはC ++コードを想定しています。ただし、それがCヘッダーの場合、コンパイラーはヘッダーファイルに含まれているデータが特定の形式(C ++の「ABI」または「アプリケーションバイナリインターフェース」)にコンパイルされることを想定しているため、リンカが起動します。これは、C ++データをCデータを期待する関数に渡すよりも望ましい方法です。

(本当に細心の注意を払うために、C ++のABIは通常、関数/メソッドの名前を「マングル」するためprintf()、プロトタイプをC関数としてフラグを立てずに呼び出すと、C ++は実際にコード呼び出しを生成_Zprintfし、最後に余分ながらくたを生成します。 )

したがって:extern "C" {...}acヘッダーを含めるときに使用します—とても簡単です。そうしないと、コンパイルされたコードに不一致が生じ、リンカが詰まってしまいます。ただし、ほとんどのヘッダーでは、externほとんどのシステムCヘッダーがC ++コードに含まれている可能性があり、すでにexternそのコードに含まれている可能性があるため、ほとんど必要ありません。


1
「ほとんどのシステムCヘッダーは、C ++コードに含まれている可能性があり、すでにそのコードをexternしているという事実をすでに説明しています。」について詳しく説明してください
Bulat M.

7
@BulatM。これらは次のようなものを含んでい #ifdef __cplusplus extern "C" { #endif ます。C++ファイルからインクルードされた場合でも、Cヘッダーとして扱われます。
Calmarius 2017年

111

extern "C"は、生成されたオブジェクトファイル内のシンボルの命名方法を決定します。関数がextern "C"なしで宣言されている場合、オブジェクトファイル内のシンボル名はC ++の名前変換を使用します。ここに例があります。

与えられたtest.Cのように:

void foo() { }

オブジェクトファイル内のシンボルをコンパイルしてリストすると、次のようになります。

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

foo関数は実際には「_Z3foov」と呼ばれます。この文字列には、特に、戻り値の型とパラメーターの型情報が含まれています。代わりにtest.Cを次のように記述した場合:

extern "C" {
    void foo() { }
}

次に、シンボルをコンパイルして確認します。

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

Cリンケージを取得します。オブジェクトファイル内の「foo」関数の名前は単なる「foo」であり、名前のマングリングから得られるファンシータイプの情報がすべて含まれているわけではありません。

それに付随するコードがCコンパイラでコンパイルされたものの、C ++から呼び出そうとしている場合は、通常、ヘッダーをextern "C" {}に含めます。これを行うと、ヘッダーのすべての宣言でCリンケージが使用されることをコンパイラーに伝えます。コードをリンクすると、.oファイルには "_Z3fooblah"ではなく "foo"への参照が含まれます。

最近のほとんどのライブラリは、このようなヘッダーの周りにガードを配置して、シンボルが正しいリンケージで宣言されるようにします。たとえば、次のような多くの標準ヘッダーにあります。

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

これにより、C ++コードにヘッダーが含まれる場合、オブジェクトファイル内のシンボルがCライブラリ内のシンボルと一致するようになります。Cヘッダーをextern "C" {}で囲む必要があるのは、それが古く、これらのガードがまだない場合のみです。


22

C ++では、名前を共有するさまざまなエンティティを使用できます。たとえば、これはすべてfooという名前の関数のリストです。

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

それらすべてを区別するために、C ++コンパイラは、名前のマングリングまたは装飾と呼ばれるプロセスで、それぞれに一意の名前を作成します。Cコンパイラはこれを行いません。さらに、各C ++コンパイラはこれを異なる方法で行う場合があります。

extern "C"は、C ++コンパイラに対して、中括弧内のコードで名前のマングリングを実行しないように指示します。これにより、C ++内からC関数を呼び出すことができます。


14

これは、さまざまなコンパイラーが名前マングリングを実行する方法に関係しています。C ++コンパイラーは、ヘッダーファイルからエクスポートされたシンボルの名前をCコンパイラーとはまったく異なる方法でマングルするため、リンクしようとすると、シンボルが欠落しているというリンカーエラーが表示されます。

これを解決するために、「C」モードで実行するようC ++コンパイラーに指示します。これにより、Cコンパイラーと同じように名前のマングリングが実行されます。これにより、リンカーエラーが修正されます。


11

CとC ++には、シンボルの名前に関する異なる規則があります。シンボルは、コンパイラによって生成された1つのオブジェクトファイル内の関数「openBankAccount」の呼び出しが、同じ(または互換)によって別のソースファイルから生成された別のオブジェクトファイル内の「openBankAccount」を呼び出した関数への参照であることをリンカーが認識する方法です。コンパイラ。これにより、複数のソースファイルからプログラムを作成できます。これは、大規模なプロジェクトで作業するときに安心です。

Cでは、ルールは非常に単純です。シンボルはすべて1つの名前空間にあります。したがって、整数「socks」は「socks」として保存され、関数count_socksは「count_socks」として保存されます。

リンカは、この単純なシンボル命名規則を使用して、CおよびCのような他の言語用に作成されました。したがって、リンカのシンボルは単純な文字列です。

しかし、C ++では、この言語を使用すると、名前空間や、ポリモーフィズムなど、このような単純なルールと競合するさまざまなことが可能になります。「add」と呼ばれる6つのすべての多態性関数には、異なるシンボルを含める必要があります。そうしないと、間違ったものが他のオブジェクトファイルで使用されます。これは、シンボルの名前を「マングリング」すること(これは専門用語です)によって行われます。

C ++コードをCライブラリまたはコードにリンクするときは、Cライブラリのヘッダーファイルなど、Cで記述されたextern "C"を使用して、これらのシンボル名がマングルされないようにC ++コンパイラに指示する必要があります。もちろん、C ++コードをマングルする必要があります。そうしないと機能しません。


11

いつ使用すべきですか?

CライブラリをC ++オブジェクトファイルにリンクする場合

それを使用する必要があるコンパイラ/リンカーレベルで何が起こっていますか?

CおよびC ++は、シンボルの命名に異なるスキームを使用します。これは、指定されたライブラリにリンクするときにCのスキームを使用するようにリンカーに指示します。

コンパイル/リンクに関して、これを使用する必要がある問題をどのように解決しますか?

Cの命名規則を使用すると、Cスタイルのシンボルを参照できます。そうしないと、リンカは機能しないC ++スタイルのシンボルを試します。


7

C ++ファイルで使用される、Cコンパイラでコンパイルされたファイルにある関数を定義するヘッダーを含める場合は、常にextern "C"を使用する必要があります。(多くの標準Cライブラリでは、ヘッダーにこのチェックを含めて、開発者にとってより簡単にすることができます)

たとえば、util.c、util.h、main.cppの3つのファイルを含むプロジェクトがあり、.cファイルと.cppファイルの両方がC ++コンパイラ(g ++、ccなど)でコンパイルされている場合は、 tは本当に必要であり、リンカーエラーを引き起こす可能性さえあります。ビルドプロセスでutil.cの通常のCコンパイラを使用する場合、util.hをインクルードするときにextern "C"を使用する必要があります。

起こっていることは、C ++がその名前で関数のパラメーターをエンコードすることです。これが関数のオーバーロードのしくみです。C関数で起こりがちなのは、名前の先頭にアンダースコア( "_")を追加することだけです。extern "C"を使用しない場合、リンカは、関数の実際の名前が_DoSomething()または単にDoSomething()である場合、DoSomething @@ int @ float()という名前の関数を探します。

extern "C"を使用すると、C ++コンパイラではなく、C ++の命名規則に従う関数を探すようにC ++コンパイラに指示することで、上記の問題が解決されます。


7

C ++コンパイラは、Cコンパイラとは異なる方法でシンボル名を作成します。したがって、CコードとしてコンパイルされたCファイルにある関数を呼び出す場合は、解決しようとしているシンボル名がデフォルトとは異なるように見えることをC ++コンパイラに伝える必要があります。そうでない場合、リンク手順は失敗します。


6

extern "C" {}構築物は、中括弧内で宣言された名前にマングリングを実行しないようにコンパイラに指示します。通常、C ++コンパイラーは関数名を「拡張」して、引数と戻り値に関する型情報をエンコードします。これをマングル名といいます。extern "C"構築物は、マングリングを防止します。

これは通常、C ++コードがC言語ライブラリを呼び出す必要がある場合に使用されます。また、(たとえばDLLから)C ++関数をCクライアントに公開するときにも使用できます。


5

これは、名前のマングリングの問題を解決するために使用されます。extern Cは、関数が「フラットな」CスタイルのAPIであることを意味します。


0

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

なぜextern必要なのかを理解するには、オブジェクトファイルで何が行われているのかを例とともに詳細に理解することが最善の方法です。

main.cpp

void f() {}
void g();

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

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

GCC 4.8 Linux ELF出力でコンパイルします。

g++ -c main.cpp

シンボルテーブルを逆コンパイルします。

readelf -s main.o

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

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

解釈

次のことがわかります。

  • efそしてeg、コードと同じ名前のシンボルに保存されました

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

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

結論:次のシンボルタイプは両方ともマングルされませんでした

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

したがってextern "C"、呼び出すときに両方が必要になります。

  • C ++からのC:g++によって生成された符号化されていないシンボルを期待するように伝えるgcc
  • CからのC ++:使用するg++ためgccに符号化されていないシンボルを生成するように指示する

extern Cで機能しないもの

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

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

完全性とそこに登場するnewbsについては、「C ++プロジェクトでCソースファイルを使用する方法」も参照してください

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

main.cpp

#include <cassert>

#include "c.h"

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

ch

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

cc

#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

せずにextern "C"リンクして失敗します。

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

生成されなかっg++たマングルを見つけることを期待しているためです。fgcc

GitHubの例

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

extern "C"それなしでは失敗します:

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

なぜならg++、生成マングルされたシンボルgccを見つけることができません。

GitHubの例

Ubuntu 18.04でテスト済み。


1
反対票を説明してくれてありがとう、それは今ではすべて理にかなっています。
Ciro Santilli郝海东冠状病六四事件法轮功
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.