使用する必要がある理由:
extern "C" {
#include <foo.h>
}
具体的には:
いつ使用する必要がありますか?
使用する必要があるコンパイラ/リンカーレベルで何が起こっていますか?
コンパイル/リンクに関して、これを使用する必要がある問題をどのように解決しますか?
CとC++は表面的には似ていますが、それぞれが非常に異なるコードセットにコンパイルされます。 C++コンパイラにヘッダーファイルを含める場合、コンパイラはC++コードを予期しています。ただし、Cヘッダーの場合、コンパイラーはヘッダーファイルに含まれるデータが特定の形式(C++ 'ABI'または 'Application Binary Interface')にコンパイルされることを期待しているため、リンカーは停止します。これは、Cデータを予期する関数にC++データを渡すよりも望ましい方法です。
(実際に核心を突くために、C++のABIは通常、関数/メソッドの名前を「マングルします」ので、C関数としてプロトタイプにフラグを立てずにprintf()
を呼び出すと、C++は実際に_Zprintf
を呼び出すコードを生成します、さらに最後に余分ながらくた。)
そのため、cヘッダーを含める場合はextern "C" {...}
を使用します。これは簡単です。そうしないと、コンパイルされたコードに不一致が生じ、リンカーが停止します。ただし、ほとんどのヘッダーについては、ほとんどのシステムCヘッダーはすでにC++コードに含まれている可能性があり、すでにコードのextern
であるため、extern
は必要ありません。
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ヘッダーが古く、これらのガードがまだない場合は、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++オブジェクトファイルにリンクする場合
使用する必要があるコンパイラ/リンカーレベルで何が起こっていますか?
CとC++は、シンボルの命名に異なるスキームを使用します。これは、指定されたライブラリでリンクするときにCのスキームを使用するようにリンカーに指示します。
コンパイル/リンクに関して、これを使用する必要がある問題をどのように解決しますか?
C命名スキームを使用すると、Cスタイルのシンボルを参照できます。そうしないと、リンカは動作しないC++スタイルのシンボルを試行します。
CとC++には、シンボルの名前に関する異なる規則があります。シンボルは、コンパイラによって生成された1つのオブジェクトファイルの関数「openBankAccount」の呼び出しが、同じ(または互換性のある)別のソースファイルから生成された別のオブジェクトファイルの「openBankAccount」コンパイラ。これにより、複数のソースファイルからプログラムを作成できます。これは、大規模なプロジェクトで作業するときに安心です。
Cでは、ルールは非常に単純で、シンボルはすべて単一の名前空間にあります。したがって、整数の「socks」は「socks」として保存され、関数count_socksは「count_socks」として保存されます。
リンカーは、この単純なシンボル命名規則を使用して、CおよびCなどの他の言語用に構築されました。したがって、リンカのシンボルは単なる文字列です。
しかし、C++では、言語を使用すると、名前空間、ポリモーフィズム、およびそのような単純なルールと競合するその他のさまざまなものを使用できます。 「add」と呼ばれる6つのポリモーフィック関数はすべて、異なるシンボルを持つ必要があります。異なるシンボルは、他のオブジェクトファイルによって使用されます。これは、シンボルの名前を「マングリング」(技術用語)することによって行われます。
C++コードをCライブラリまたはコードにリンクする場合、Cライブラリのヘッダーファイルなど、Cで記述されたextern "C"が必要です。これらのシンボル名はマングルされないことをC++コンパイラに伝えます。もちろん、C++コードをマングルする必要があります。そうしないと機能しません。
C++コンパイラは、Cコンパイラとは異なる方法でシンボル名を作成します。したがって、CコードとしてコンパイルされたCファイルに存在する関数を呼び出す場合、解決しようとしているシンボル名がデフォルトとは異なるように見えることをC++コンパイラに伝える必要があります。そうでない場合、リンク手順は失敗します。
C++ファイルで使用されるCコンパイラーによってコンパイルされたファイルに存在する関数を定義するヘッダーをインクルードするときはいつでも、extern "C"を使用する必要があります。 (多くの標準Cライブラリでは、開発者がより簡単にするために、ヘッダーにこのチェックを含めることができます)
たとえば、3つのファイル、util.c、util.h、およびmain.cppを含むプロジェクトがあり、.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++コンパイラに伝えることにより、上記の問題を解決します。
extern "C" {}
構文は、中括弧内で宣言された名前に対してマングリングを実行しないようコンパイラーに指示します。通常、C++コンパイラは関数名を「拡張」して、引数と戻り値に関する型情報をエンコードします。これはマングル名と呼ばれます。 extern "C"
構文は、マングリングを防ぎます。
通常、C++コードがC言語ライブラリを呼び出す必要がある場合に使用されます。 CクライアントにC++関数を(たとえば、DLLから)公開するときにも使用できます。
これは名前のマングリングの問題を解決するために使用されます。 extern Cは、関数が「フラットな」CスタイルAPIであることを意味します。
g++
生成されたバイナリを逆コンパイルして、何が起こっているかを確認します
C++でのextern "C"の効果は何ですか? この質問はこの質問の複製と考えられていたため、私はこの回答に移動しています。
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()
結論:次のシンボルタイプは両方ともnotマングルされました:
Ndx = UND
)、リンクまたは実行時に別のオブジェクトファイルから提供されるそのため、呼び出すときにextern "C"
の両方が必要になります。
g++
に、gcc
によって生成されたマングルされていないシンボルを期待するように指示します。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);
}
c.h
#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
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
extern "C"
がなければ、リンクは次のように失敗します:
main.cpp:6: undefined reference to `f()'
g++
は、f
が生成しなかったマングルされたgcc
を見つけることを期待しているためです。
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でテスト済み。