回答:
CとC ++は表面的には似ていますが、それぞれが非常に異なるコードセットにコンパイルされます。C ++コンパイラにヘッダーファイルを含める場合、コンパイラはC ++コードを想定しています。ただし、それがCヘッダーの場合、コンパイラーはヘッダーファイルに含まれているデータが特定の形式(C ++の「ABI」または「アプリケーションバイナリインターフェース」)にコンパイルされることを想定しているため、リンカが起動します。これは、C ++データをCデータを期待する関数に渡すよりも望ましい方法です。
(本当に細心の注意を払うために、C ++のABIは通常、関数/メソッドの名前を「マングル」するためprintf()
、プロトタイプをC関数としてフラグを立てずに呼び出すと、C ++は実際にコード呼び出しを生成_Zprintf
し、最後に余分ながらくたを生成します。 )
したがって:extern "C" {...}
acヘッダーを含めるときに使用します—とても簡単です。そうしないと、コンパイルされたコードに不一致が生じ、リンカが詰まってしまいます。ただし、ほとんどのヘッダーでは、extern
ほとんどのシステムCヘッダーがC ++コードに含まれている可能性があり、すでにextern
そのコードに含まれている可能性があるため、ほとんど必要ありません。
#ifdef __cplusplus extern "C" { #endif
ます。C++ファイルからインクルードされた場合でも、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」であり、名前のマングリングから得られるファンシータイプの情報がすべて含まれているわけではありません。
それに付随するコードがCコンパイラでコンパイルされたものの、C ++から呼び出そうとしている場合は、通常、ヘッダーをextern "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 ++には、シンボルの名前に関する異なる規則があります。シンボルは、コンパイラによって生成された1つのオブジェクトファイル内の関数「openBankAccount」の呼び出しが、同じ(または互換)によって別のソースファイルから生成された別のオブジェクトファイル内の「openBankAccount」を呼び出した関数への参照であることをリンカーが認識する方法です。コンパイラ。これにより、複数のソースファイルからプログラムを作成できます。これは、大規模なプロジェクトで作業するときに安心です。
Cでは、ルールは非常に単純です。シンボルはすべて1つの名前空間にあります。したがって、整数「socks」は「socks」として保存され、関数count_socksは「count_socks」として保存されます。
リンカは、この単純なシンボル命名規則を使用して、CおよびCのような他の言語用に作成されました。したがって、リンカのシンボルは単純な文字列です。
しかし、C ++では、この言語を使用すると、名前空間や、ポリモーフィズムなど、このような単純なルールと競合するさまざまなことが可能になります。「add」と呼ばれる6つのすべての多態性関数には、異なるシンボルを含める必要があります。そうしないと、間違ったものが他のオブジェクトファイルで使用されます。これは、シンボルの名前を「マングリング」すること(これは専門用語です)によって行われます。
C ++コードをCライブラリまたはコードにリンクするときは、Cライブラリのヘッダーファイルなど、Cで記述されたextern "C"を使用して、これらのシンボル名がマングルされないようにC ++コンパイラに指示する必要があります。もちろん、C ++コードをマングルする必要があります。そうしないと機能しません。
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 ++コンパイラに指示することで、上記の問題が解決されます。
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"
、呼び出すときに両方が必要になります。
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);
}
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++
たマングルを見つけることを期待しているためです。f
gcc
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
を見つけることができません。
Ubuntu 18.04でテスト済み。