私はプログラミング言語コースを受講しており、extern "C"
宣言。
この宣言は、「CとC++のインターフェイス」以外のより深いレベルでどのように機能しますか?これは、プログラムで行われるバインディングにもどのように影響しますか?
_extern "C"
_は、後続のシンボルが マングル (装飾されていない)でないことを確認するために使用されます。
例:
_test.cpp
_というファイルに次のコードがあるとします。
_extern "C" {
int foo() {
return 1;
}
}
int bar() {
return 1;
}
_
_gcc -c test.cpp -o test.o
_を実行すると
シンボル名を見てください:
00000010 T _Z3barv
00000000 T foo
foo()
はその名前を保持します。
CとC++の両方でコンパイルできる典型的な関数を見てみましょう。
int Add (int a, int b)
{
return a+b;
}
現在、Cではこの関数は内部的に "_Add"と呼ばれています。一方、C++関数は、名前のマングリングと呼ばれるシステムを使用して、内部では完全に異なるものと呼ばれます。基本的には、関数に名前を付ける方法です。これにより、異なるパラメーターを持つ同じ関数が異なる内部名を持つようになります。
したがって、add()がadd.cで定義されており、add.hにプロトタイプがある場合、C++ファイルにadd.hを含めようとすると問題が発生します。 C++コードがadd.cの名前とは異なる名前の関数を探しているため、リンカーエラーが発生します。この問題を回避するには、次の方法でadd.cを含める必要があります。
extern "C"
{
#include "add.h"
}
これで、C++コードは、C++名のマングルバージョンではなく_Addとリンクします。
それが表現の使い方の一つです。結論として、C++プログラムで厳密にCであるコードをコンパイルする必要がある場合(includeステートメントまたはその他の手段を使用)、それをextern "C" {...}宣言でラップする必要があります。
コードのブロックにextern "C"のフラグを立てると、Cスタイルのリンケージを使用するようにシステムに指示することになります。
これは主に、リンカーが名前をマングルする方法に影響します。 C++スタイルの名前のマングリング(演算子のオーバーロードをサポートするにはより複雑)を使用する代わりに、リンカーから標準のCスタイルの名前を取得します。
C++では、関数の名前/シンボルは実際には別の名前に変更され、異なるクラス/名前空間が同じシグネチャの関数を持つことができます。 Cでは、関数はすべてグローバルに定義されており、そのようなカスタマイズされた名前変更プロセスは必要ありません。
C++とCが互いに対話できるようにするために、「extern C」はCの規則を使用しないようにコンパイラーに指示します。
extern "C"
も関数のタイプを変更することに注意してください。それはより低いレベルで物事を変更するだけではありません:
extern "C" typedef void (*function_ptr_t)();
void foo();
int main() { function_ptr_t fptr = &foo; } // error!
&foo
のタイプは、typedefが指定するタイプとは異なります(ただし、コードは一部のコンパイラーでは受け入れられますが、すべてのコンパイラーでは受け入れられません)。
extern Cは、C++コンパイラによる名前のマングリングに影響します。これは、C++コンパイラが名前をマングルしないようにする方法、またはCコンパイラと同じ方法で名前をマングルする方法です。これは、CおよびC++とのインターフェース方法です。
例として:
extern "C" void foo(int i);
関数をCモジュールで実装できるようにしますが、C++モジュールから呼び出すことはできます。
CモジュールにC++モジュールで定義されたC++関数(明らかにCはC++クラスを使用できない)を呼び出そうとすると、問題が発生します。 Cコンパイラはextern "C"
を好みません。
したがって、これを使用する必要があります:
#ifdef __cplusplus
extern "C" {
#endif
void foo(int i);
#ifdef __cplusplus
}
#endif
これがヘッダーファイルに表示されると、CコンパイラとC++コンパイラの両方が宣言に満足し、CまたはC++モジュールのいずれかで定義できるようになり、CおよびC++コードの両方から呼び出すことができます。
extern "C"は、囲まれたコードがCスタイルのリンクと名前のマングリングを使用することを示します。 C++は、より複雑な名前変換形式を使用します。次に例を示します。
http://en.wikipedia.org/wiki/Name_mangling
int example(int alpha, char beta);
c:_example
c ++の場合:__Z7exampleic
更新:GManNickGがコメントで注記しているように、名前マングリングのパターンはコンパイラーに依存します。
extern "C"は、Cバインディングを使用して関数を宣言するためのキーワードです。これは、CコンパイラとC++コンパイラがソースをオブジェクトファイル内の異なる形式に変換するためです。
たとえば、コードスニペットは次のとおりです。
int _cdecl func1(void) {return 0}
int _stdcall func2(int) {return 0}
int _fastcall func3(void) {return 1}
32ビットCコンパイラは、コードを次の形式で変換します。
_func1
_func2@4
@func3@4
cdeclでは、func1は '_ name'として変換されます
stdcallでは、func2は '_ name @ X'として変換されます
fastcallでは、func2は '@ name @ X'として変換されます
'[〜#〜] x [〜#〜]'は、パラメーターリスト内のパラメーターのバイト数を意味します。
Windowsでの64ビット規約には、先頭にアンダースコアがありません
C++では、クラス、テンプレート、名前空間、および演算子のオーバーロードが導入されています。これは、同じ名前の2つの関数が許可されていないため、C++コンパイラがシンボル名に型情報を提供します。
たとえば、コードスニペットは次のとおりです。
int func(void) {return 1;}
int func(int) {return 0;}
int func_call(void) {int m=func(), n=func(0);}
C++コンパイラは、コードを次のように変換します。
int func_v(void) {return 1;}
int func_i(int) {return 0;}
int func_call(void) {int m=_func_v(), n=_func_i(0);}
「_v」と「_i」は「void」と「int」の型情報です