なぜ使用する必要があるのですか:
extern "C" {
#include <foo.h>
}
具体的には:
いつ使用する必要がありますか?
それを使用する必要があるコンパイラ/リンカーレベルで何が起こっているのですか?
コンパイル/リンクに関して、これを使用する必要がある問題をどのように解決しますか?
なぜ使用する必要があるのですか:
extern "C" {
#include <foo.h>
}
具体的には:
いつ使用する必要がありますか?
それを使用する必要があるコンパイラ/リンカーレベルで何が起こっているのですか?
コンパイル/リンクに関して、これを使用する必要がある問題をどのように解決しますか?
CとC++は表面的には似ていますが、それぞれが非常に異なるコードのセットにコンパイルされます。C ++コンパイラでヘッダーファイルをインクルードする場合、コンパイラはC++コードを期待しています。ただし、それがCヘッダーの場合、コンパイラーは、ヘッダーファイルに含まれるデータが特定の形式(C ++の「ABI」または「アプリケーションバイナリインターフェイス」)にコンパイルされることを想定しているため、リンカーが機能しなくなります。これは、Cデータを期待する関数にC++データを渡すよりも望ましい方法です。
(本当に本質的なことを理解するために、C ++のABIは通常、関数/メソッドの名前を「マングル」します。したがってprintf()
、プロトタイプをC関数としてフラグを立てずに呼び出すと、C ++は実際にコード呼び出しを生成_Zprintf
し、最後に余分ながらくたを生成します。 )。
つまりextern "C" {...}
、acヘッダーを含めるときに使用します。これは非常に簡単です。そうしないと、コンパイルされたコードに不一致が生じ、リンカーがチョークします。ただし、ほとんどのヘッダーでは、extern
ほとんどのシステムCヘッダーは、C ++コードとそのコードに含まれている可能性があるという事実をすでに考慮しているため、必要ありませんextern "C"
。
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」であり、名前マングリングから得られる凝った型情報がすべて含まれているわけではありません。
通常、extern "C" {} 内にヘッダーを含めます。ヘッダーに付随するコードが C コンパイラでコンパイルされているが、C++ から呼び出そうとする場合です。これを行うと、ヘッダー内のすべての宣言が C リンケージを使用することをコンパイラーに伝えます。コードをリンクすると、.o ファイルには「_Z3fooblah」ではなく「foo」への参照が含まれます。これは、リンク先のライブラリにあるものと一致することを願っています。
最新のライブラリのほとんどは、そのようなヘッダーの周りにガードを配置して、シンボルが正しいリンケージで宣言されるようにします。たとえば、多くの標準ヘッダーに次のようなものがあります。
#ifdef __cplusplus
extern "C" {
#endif
... declarations ...
#ifdef __cplusplus
}
#endif
これにより、C++ コードにヘッダーが含まれている場合、オブジェクト ファイル内のシンボルが C ライブラリ内のものと一致することが保証されます。古いもので、これらのガードがまだない場合にのみ、C ヘッダーの周りに extern "C" {} を配置する必要があります。
C ++では、名前を共有するさまざまなエンティティを持つことができます。たとえば、これはすべてfooという名前の関数のリストです。
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
それらすべてを区別するために、C ++コンパイラは、名前のマングリングまたは装飾と呼ばれるプロセスで、それぞれに一意の名前を作成します。Cコンパイラはこれを行いません。さらに、各C++コンパイラはこれを行う場合があります。これは異なる方法です。
extern "C"は、中括弧内のコードで名前マングリングを実行しないようにC++コンパイラに指示します。これにより、C++内からC関数を呼び出すことができます。
これは、さまざまなコンパイラが名前マングリングを実行する方法に関係しています。C++ コンパイラは、ヘッダー ファイルからエクスポートされたシンボルの名前を、C コンパイラとはまったく異なる方法でマングルするため、リンクしようとすると、シンボルが見つからないというリンカ エラーが発生します。
これを解決するために、C++ コンパイラに「C」モードで実行するように指示し、C コンパイラと同じ方法で名前マングリングを実行します。そうすることで、リンカのエラーが修正されます。
CとC++には、シンボルの名前に関する異なる規則があります。シンボルは、コンパイラによって生成された1つのオブジェクトファイルでの関数「openBankAccount」の呼び出しが、同じ(または互換性のある)別のソースファイルから生成された別のオブジェクトファイルで「openBankAccount」と呼ばれた関数への参照であることをリンカが認識する方法です。コンパイラ。これにより、複数のソースファイルからプログラムを作成できます。これは、大規模なプロジェクトで作業する場合に安心です。
Cでは、ルールは非常に単純で、シンボルはすべて単一の名前空間にあります。したがって、整数の「socks」は「socks」として格納され、関数count_socksは「count_socks」として格納されます。
リンカーは、この単純な記号命名規則を使用して、CおよびCなどの他の言語用に構築されました。したがって、リンカのシンボルは単なる文字列です。
しかし、C ++では、この言語を使用すると、名前空間、ポリモーフィズム、およびそのような単純なルールと競合するその他のさまざまなものを使用できます。「追加」と呼ばれる6つのポリモーフィック関数はすべて、異なるシンボルを持つ必要があります。そうでない場合、他のオブジェクトファイルで間違ったシンボルが使用されます。これは、シンボルの名前を「マングリング」(技術用語)することによって行われます。
C ++コードをCライブラリまたはコードにリンクするときは、Cライブラリのヘッダーファイルなど、Cで記述されたものをextern "C"して、これらのシンボル名がマングルされないようにC++コンパイラに通知する必要があります。もちろん、C ++コードはマングルする必要があります。そうしないと、機能しません。
いつ使うべきですか?
CライブラリをC++オブジェクトファイルにリンクする場合
コンパイラ/リンカーレベルで何が起こっているので、それを使用する必要がありますか?
CとC++は、シンボルの命名に異なるスキームを使用します。これは、指定されたライブラリでリンクするときにCのスキームを使用するようにリンカに指示します。
コンパイル/リンクに関して、これは私たちがそれを使用する必要がある問題をどのように解決しますか?
C命名スキームを使用すると、Cスタイルのシンボルを参照できます。そうしないと、リンカは機能しないC++スタイルのシンボルを試行します。
C++ ファイルで使用される、C コンパイラによってコンパイルされたファイルに常駐する関数を定義するヘッダーをインクルードするときはいつでも、extern "C" を使用する必要があります。(多くの標準 C ライブラリは、開発者にとってより簡単にするために、ヘッダーにこのチェックを含めることができます)
たとえば、util.c、util.h、および main.cpp の 3 つのファイルを含むプロジェクトがあり、.c ファイルと .cpp ファイルの両方が C++ コンパイラ (g++、cc など) でコンパイルされている場合は、そうではありません。これは本当に必要であり、リンカ エラーを引き起こす可能性さえあります。ビルド プロセスで util.c に通常の C コンパイラを使用する場合、util.h をインクルードするときに extern "C" を使用する必要があります。
起こっていることは、C++ が関数のパラメーターをその名前でエンコードすることです。これが関数のオーバーロードの仕組みです。C 関数で起こりがちなことは、名前の先頭にアンダースコア ("_") を追加することだけです。extern "C" を使用しないと、関数の実際の名前が _DoSomething() または単に DoSomething() である場合、リンカは DoSomething@@int@float() という名前の関数を探します。
extern "C" を使用すると、C++ の命名規則ではなく C の命名規則に従う関数を探すように C++ コンパイラに指示することで、上記の問題を解決できます。
C++ コンパイラは、C コンパイラとは異なる方法でシンボル名を作成します。そのため、C コードとしてコンパイルされた C ファイルにある関数を呼び出そうとする場合は、解決しようとしているシンボル名がデフォルトとは異なるように見えることを C++ コンパイラに伝える必要があります。そうしないと、リンク ステップが失敗します。
構成はextern "C" {}
、中括弧内で宣言された名前に対してマングリングを実行しないようにコンパイラーに指示します。通常、C ++コンパイラは関数名を「拡張」して、引数と戻り値に関する型情報をエンコードします。これはマングル名と呼ばれます。extern "C"
コンストラクトはマングリングを防ぎます。
これは通常、C++コードがC言語ライブラリを呼び出す必要がある場合に使用されます。また、C ++関数(たとえば、DLLから)をCクライアントに公開するときにも使用できます。
これは、名前マングリングの問題を解決するために使用されます。extern C は、関数が「フラットな」C スタイルの API であることを意味します。