web-dev-qa-db-ja.com

C++でのextern "C"の効果は何ですか?

extern "C"をC++コードに正確に入れると何ができますか?

例えば:

extern "C" {
   void foo();
}
1390
Litherum

extern "C"は、C++の関数名に 'C'リンケージを持たせる(コンパイラは名前を変換しない)ので、クライアントCコードは、C言語互換のヘッダファイルだけを使って関数にリンク(つまり使用)できます。あなたの関数の宣言あなたの関数定義は(C++コンパイラによってコンパイルされた)バイナリフォーマットで含まれていて、クライアントの 'C'リンカはそれから 'C'名を使ってリンクするでしょう。

C++は関数名をオーバーロードし、Cはオーバーロードしないため、C++コンパイラはリンクする一意のIDとして関数名を使用することはできません。そのため、引数に関する情報を追加することによって名前を壊します。 C++では関数名をオーバーロードすることはできないので、Cコンパイラは名前を変換する必要はありません。リンケージ。

ご存じのとおり、個々の宣言/定義に "C"リンケージを明示的に指定するか、ブロックを使用して一連の宣言/定義をグループ化して特定のリンケージを作成できます。

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

技術的なことを気にしているのであれば、それらはC++ 03標準のセクション7.5にリストされています、ここで簡単な要約です(extern "C"を強調して)

  • extern "C"はリンケージ指定です
  • "C"リンケージを提供するには、すべてのコンパイラは 必須 です。
  • リンケージ指定は名前空間の有効範囲内でのみ行わなければならない
  • すべての関数型、関数名、および変数名は言語リンケージを持ちますリチャードのコメントを見る: 外部リンケージを持つ関数名と変数名だけが言語リンケージを持ちます
  • 異なる言語リンケージを持つ2つの関数型は、それ以外は同一であっても異なる型
  • リンケージ仕様がネストし、内側のものが最終的なリンケージを決定します
  • クラスメンバーの場合、extern "C"は無視されます
  • 特定の名前を持つ最大1つの関数は、(名前空間に関係なく) "C"リンケージを持つことができます。
  • extern "C"は関数に外部リンケージを強制する(静的にはできない)リチャードのコメントを見る: 'extern "C"'内の 'static'は有効です。そのように宣言された実体は内部リンケージを持ち、言語リンケージを持ちません
  • C++から他の言語で定義されたオブジェクト、および他の言語からC++で定義されたオブジェクトへのリンクは、実装定義および言語依存です。 2つの言語実装のオブジェクトレイアウト戦略が十分に類似している場合にのみ、そのようなリンケージを達成することができます。
1373
Faisal Vali

まだ投稿されていないので、少し情報を追加したいと思いました。

あなたは非常に頻繁にCヘッダのようにコードを見るでしょう:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

これにより、マクロ "__cplusplus"が定義されるため、CヘッダーファイルをC++コードで使用できるようになります。しかし、マクロはNOTで定義されているので、alsoはまだあなたのレガシーなCコードと一緒に使うことができます。

とはいえ、私は以下のようなC++コードも見ました。

extern "C" {
#include "legacy_C_header.h"
}

それは私が想像しているのとほぼ同じことです。

どちらが良いのかわからないが、私は両方を見たことがある。

278
UncaAlby

すべてのC++プログラムで、すべての非静的関数はバイナリファイルでシンボルとして表されます。これらの記号は、プログラム内の機能を一意に識別する特別なテキスト文字列です。

Cでは、シンボル名は関数名と同じです。 Cでは、2つの非静的関数が同じ名前を持つことはできないため、これが可能です。

C++はオーバーロードを許可し、Cにはない多くの機能(クラス、メンバー関数、例外の仕様など)を備えているため、単に関数名をシンボル名として使用することはできません。これを解決するために、C++は、いわゆる名前マングリングを使用します。これは、関数名とすべての必要な情報(引数の数やサイズなど)を、コンパイラとリンカだけが処理する奇妙な文字列に変換します。

したがって、関数をextern Cに指定した場合、コンパイラはそれを使用して名前マングリングを実行せず、関数名としてそのシンボル名を使用して直接アクセスできます。

そのような関数を呼び出すためにdlsym()dlopen()を使うとき、これは役に立ちます。

195
sud03r

g++が生成したバイナリを逆コンパイルして、何が起こっているのかを確かめます

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

解釈

それがわかります:

  • efegは、コード内と同じ名前のシンボルに格納されていました。

  • 他のシンボルは破壊されました。それらを分解しましょう。

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

結論:以下のシンボルタイプは両方とも{not}にマングルされていました。

  • 定義済み
  • 別のオブジェクトファイルからリンク時または実行時に提供されるように宣言されているが未定義(Ndx = UND

そのため、呼び出すときには両方ともextern "C"が必要になります。

  • C++からC:gccによって生成されたアンマングルドシンボルを期待するようにg++に伝えます
  • CからC++:gccが使用するアンマングルドシンボルを生成するようにg++に指示します

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の例

完全を期すために、またそこから出てくる小説については、以下も参照してください。 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でテスト済み。

手続き型言語からオブジェクト指向言語を作成するためのC++による関数名の変換

ほとんどのプログラミング言語は、既存のプログラミング言語の上に構築されていません。 C++はCの上に構築されており、さらに手続き型プログラミング言語から構築されたオブジェクト指向プログラミング言語であり、そのためCとの後方互換性を提供するexternのようなC++キーワードがあります。

次の例を見てみましょう。

#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

同じ関数printMeが2回定義されているため、Cコンパイラーは上記の例をコンパイルしません(それらが異なるパラメーターint achar aを持っていても)。

gcc -o printMe printMe.c && ./printMe;
1エラー。 PrintMeが複数回定義されています。

C++コンパイラは上記の例をコンパイルします。 printMeが2回定義されていても構いません。

g ++ -o printMe printMe.c && ./printMe;

これは、C++コンパイラが暗黙的に( mangles )関数の名前をそのパラメータに基づいて)変更したためです。Cでは、この機能はサポートされていませんでした。同じ名前のメソッド(関数)で異なるクラスを作成し、異なるパラメータに基づいてメソッド( method overriding ))をオーバーライドする機能をサポートする必要があります。

Externは「関数名を破壊しない」と言っています

しかし、includesが他の従来のCファイルからの名前である、 "parent.c"という名前の従来のCファイル、 "parent.h"、 "child.h"などがあるとします。従来の "parent.c"ファイルが実行される場合C++コンパイラを使用すると、関数名は壊され、 "parent.h"、 "child.h"などで指定された関数名と一致しなくなります。そのため、これらの外部ファイル内の関数名も必要になります。こわされる。複雑なCプログラム(関数依存関係が多いもの)で関数名を乱すと、コードが壊れる可能性があります。そのため、関数名を乱さないようにC++コンパイラに指示できるキーワードを指定すると便利な場合があります。

externキーワードは、C++コンパイラに関数名を変換しないように指示します。使用例:extern void printMe(int a);

38
tfmontague

関数がCから呼び出せるように関数のリンケージを変更します。実際には、関数名は mangled ではありません。

26

Extern "C"をラップするだけでは、CヘッダーとC++の互換性を保つことはできません。 Cヘッダー内の識別子がC++キーワードと競合する場合、C++コンパイラはこれについて文句を言います。

例えば、私は次のコードがg ++で失敗するのを見ました:

extern "C" {
struct method {
    int virtual;
};
}

Kindaは理にかなっていますが、CコードをC++に移植するときに留意する必要があります。

25
Sander Mertens

CとC++でコンパイルされた関数の名前はリンク段階で異なるため、リンク時にこれらの関数の名前をCスタイルで検索するようにC++コンパイラに通知します。

19
Mark Rushakoff

extern "C"は、C++コンパイラによって認識され、指定された関数がCスタイルでコンパイルされている(またはコンパイルされる)ことをコンパイラに通知することを意味します。そのため、リンクしている間は、Cから正しいバージョンの関数にリンクされます。

12
Flami

私は以前に 'extern "C"をdll(ダイナミックリンクライブラリ)ファイルなどにmain()関数を "エクスポート可能"にするために使用していたので、後でdllの別の実行ファイルで使用することができます。たぶん私がそれを使っていた場所の例は役に立つかもしれません。

DLL

#include <string.h>
#include <windows.h>

using namespace std;

#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
    MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}

EXE

#include <string.h>
#include <windows.h>

using namespace std;

typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder

int main()
{
    char winDir[MAX_PATH];//will hold path of above dll
    GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
    strcat(winDir,"\\exmple.dll");//concentrate dll name with path
    HINSTANCE DLL = LoadLibrary(winDir);//load example dll
    if(DLL==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if load fails exit
        return 0;
    }
    mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
    //defined variable is used to assign a function from dll
    //GetProcAddress is used to locate function with pre defined extern name "DLL"
    //and matcing function name
    if(mainDLLFunc==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if it fails exit
        return 0;
    }
    mainDLLFunc();//run exported function 
    FreeLibrary((HMODULE)DLL);
}
6
SturmCoder

extern "C"は、Cppソースファイル内のC関数の呼び出しに使用されるリンケージ指定です。 C関数の呼び出し、変数の記述、ヘッダーのインクルード _することができます。関数が外部実体で宣言されていて、外部で定義されています。構文は

タイプ1:

extern "language" function-prototype

タイプ2:

extern "language"
{
     function-prototype
};

例:

#include<iostream>
using namespace std;

extern "C"
{
     #include<stdio.h>    // Include C Header
     int n;               // Declare a Variable
     void func(int,int);  // Declare a function (function prototype)
}

int main()
{
    func(int a, int b);   // Calling function . . .
    return 0;
}

// Function definition . . .
void func(int m, int n)
{
    //
    //
}
5
Yogeesh H T

この答えは、せっかちな人のためのものです。

  • c ++では、オーバーロードによってクラス内に同じ名前を付けることができます(たとえば、これらはすべて同じ名前であるため、dllからエクスポートすることはできません)。これらの問題に対する解決策は異なる文字列に変換されます(シンボルと呼ばれます) )、シンボルは関数の名前、引数も考慮に入れるので、これらの関数はそれぞれ同じ名前であっても一意に識別できます(名前マングルとも呼ばれます)。
  • cでは、オーバーロードする必要はありません。関数名は一意です(したがって、関数名を一意に識別するための別の文字列は不要です。したがって、シンボルは関数名そのものです)。

そう
C++では、名前マングリングは各関数を一意に識別します。
C言語では、名前のマングリングがなくても、各機能は一意に識別されます。

C++の振る舞いを変更する、つまり、特定の関数に対して名前マングリングしてはいけないが起こるように指定するには、なんらかの理由で関数名の前にextern "C"を使用できます。そのクライアントが使用するために、DLLから特定の名前の関数をエクスポートするようなものです。

より詳細な/より正しい答えについては、他の答えを読んでください。

CとC ++とを混合するとき(すなわち、C ++からC関数を呼び出すこと;およびb.CからC ++関数を呼び出すこと)、C ++名前マングリングはリンク問題を引き起こす。技術的には、この問題は、呼び出し先の関数が対応するコンパイラを使用して既にバイナリ(ほとんどの場合、*。aライブラリファイル)にコンパイルされている場合にのみ発生します。

そのため、C++で名前マングリングを無効にするには、extern "C"を使用する必要があります。

1
Trombe