web-dev-qa-db-ja.com

extern "C"宣言はどのように機能しますか?

私はプログラミング言語コースを受講しており、extern "C"宣言。

この宣言は、「CとC++のインターフェイス」以外のより深いレベルでどのように機能しますか?これは、プログラムで行われるバインディングにもどのように影響しますか?

27
samoz

_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()はその名前を保持します。

46
Bertrand Marron

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" {...}宣言でラップする必要があります。

25
Cthutu

コードのブロックにextern "C"のフラグを立てると、Cスタイルのリンケージを使用するようにシステムに指示することになります。

これは主に、リンカーが名前をマングルする方法に影響します。 C++スタイルの名前のマングリング(演算子のオーバーロードをサポートするにはより複雑)を使用する代わりに、リンカーから標準のCスタイルの名前を取得します。

9
Reed Copsey

C++では、関数の名前/シンボルは実際には別の名前に変更され、異なるクラス/名前空間が同じシグネチャの関数を持つことができます。 Cでは、関数はすべてグローバルに定義されており、そのようなカスタマイズされた名前変更プロセスは必要ありません。

C++とCが互いに対話できるようにするために、「extern C」はCの規則を使用しないようにコンパイラーに指示します。

5
gilbertc

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++コードの両方から呼び出すことができます。

4
quamrana

extern "C"は、囲まれたコードがCスタイルのリンクと名前のマングリングを使用することを示します。 C++は、より複雑な名前変換形式を使用します。次に例を示します。

http://en.wikipedia.org/wiki/Name_mangling

int example(int alpha, char beta);

c:_example

c ++の場合:__Z7exampleic

更新:GManNickGがコメントで注記しているように、名前マングリングのパターンはコンパイラーに依存します。

3
Harvey

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」の型情報です

0
Dongwei Wang