extern“ C”の効果は何ですかC ++で?
-
22-07-2019 - |
質問
extern" C"
をC ++コードに正確に入れるとどうなりますか?
例:
extern "C" {
void foo();
}
解決
extern" C" C ++の関数名に 'C'リンケージを持たせる(コンパイラは名前を変更しない)ため、クライアントCコードは、関数の宣言のみを含む 'C'互換ヘッダーファイルを使用して関数にリンク(使用)できます。 。関数定義は(C ++コンパイラによってコンパイルされた)バイナリ形式に含まれており、クライアントの「C」リンカーは「C」名を使用してリンクします。
C ++には関数名のオーバーロードがあり、Cにはオーバーロードがないため、C ++コンパイラはリンクする一意のIDとして関数名を使用することはできないため、引数に関する情報を追加して名前を変更します。 Cで関数名をオーバーロードできないため、Cコンパイラは名前を変更する必要がありません。関数にextern" C" 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つの言語実装のオブジェクトレイアウト戦略が十分に類似している場合にのみ、このようなリンクを実現できます
他のヒント
まだ投稿されていないので、少し情報を追加したかったです。
次のようなコードがCヘッダーに頻繁に表示されます。
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
これにより、CヘッダーファイルをC ++コードで使用できるようになります。マクロ" __ cplusplus"定義されます。ただし、マクロが NOT 定義されているレガシーCコードでも 使用できます。そのため、一意のC ++コンストラクトは表示されません。
ただし、次のようなC ++コードも見ました:
extern "C" {
#include "legacy_C_header.h"
}
これはほぼ同じことを達成すると思います。
どちらが良いかはわかりませんが、両方を見ました。
すべてのC ++プログラムでは、すべての非静的関数はバイナリファイルでシンボルとして表されます。これらのシンボルは、プログラム内の関数を一意に識別する特別なテキスト文字列です。
Cでは、シンボル名は関数名と同じです。これは、Cでは2つの非静的関数に同じ名前を付けることができないためです。
C ++はオーバーロードを許可し、クラス、メンバー関数、例外仕様など、Cにはない多くの機能があるため、単に関数名をシンボル名として使用することはできません。これを解決するために、C ++はいわゆる名前マングリングを使用します。これは、関数名とすべての必要な情報(引数の数やサイズなど)を、コンパイラーとリンカーによってのみ処理される奇妙な文字列に変換します。
したがって、関数を外部Cに指定すると、コンパイラーはそれを使用して名前のマングリングを実行せず、直接実行できます シンボル名を関数名として使用してアクセスします。
これは、 dlsym()
および dlopen()
を使用してこのような関数を呼び出すときに便利です。
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
解釈
次のことがわかります:
-
ef
およびeg
は、コードと同じ名前のシンボルに保存されました -
他のシンボルはマングルされました。それらを解きましょう:
$ c++filt _Z1fv f() $ c++filt _Z1hv h() $ c++filt _Z1gv g()
結論:次のシンボルタイプは両方ともマングルされていません :
- 定義
- 宣言されているが未定義(
Ndx = UND
)、リンクまたは実行時に別のオブジェクトファイルから提供される
そのため、呼び出し時には extern" C"
の両方が必要になります:
- C ++からのC:
gcc
によって生成されたマングルされていないシンボルを期待するように - CのC ++:
g ++
に、使用するgcc
のマングルされていないシンボルを生成するように指示します
g ++
に伝えます
外部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&quot; C&quot;
なしでは、リンクは次のように失敗します:
main.cpp:6: undefined reference to `f()'
g ++
は、 gcc
が生成しなかった、壊れた f
を見つけることを期待しているため。
Cからの最小限の実行可能な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&quot; C&quot;
がなければ、次のエラーで失敗します:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
g ++
は gcc
が見つけられないマングル記号を生成したため。
Ubuntu 18.04でテスト済み。
C ++は関数名をマングルして、手続き型言語からオブジェクト指向言語を作成します
ほとんどのプログラミング言語は、既存のプログラミング言語の上に構築されていません。 C ++はCの上に構築され、さらに手続き型プログラミング言語から構築されたオブジェクト指向プログラミング言語です。そのため、下位互換性を提供する extern&quot; C&quot;
のようなC ++式があります。 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;
}
ACコンパイラは、同じ関数 printMe
が2回定義されているため、上記の例をコンパイルしません(パラメータ int a
と char a
)。
gcc -o printMe printMe.c&amp;&amp; ./printMe;
1エラー。 PrintMeが複数回定義されています。
C ++コンパイラは上記の例をコンパイルします。 printMe
が2回定義されているかどうかは気にしません。
g ++ -o printMe printMe.c&amp;&amp; ./printMe;
これは、C ++コンパイラがパラメータに基づいて関数の名前を暗黙的に変更する(マングル)ためです。 Cでは、この機能はサポートされていませんでした。ただし、C ++がC上に構築された場合、言語はオブジェクト指向であるように設計され、同じ名前のメソッド(関数)で異なるクラスを作成し、メソッドをオーバーライドする機能をサポートする必要がありました(メソッドのオーバーライド)さまざまなパラメーターに基づいています。
extern&quot; C&quot;
は、「C関数名をマングルしないでください」
ただし、「parent.c」という名前のレガシーCファイルがあるとします。その include
は、他のレガシーCファイルからの関数名、「parent.h」、「child.h」、などです。レガシー「parent.c」がファイルはC ++コンパイラを介して実行され、関数名はマングルされ、&quot; parent.h&quot;、&quot; child.h&quot;などで指定された関数名と一致しなくなります。ファイルもマングルする必要があります。依存関係の多い複雑なCプログラムで関数名をマングリングすると、コードが破損する可能性があります。そのため、C ++コンパイラに関数名を変更しないように指示できるキーワードを提供すると便利です。
extern&quot; C&quot;
キーワードは、C ++コンパイラにC関数名をマングル(名前変更)しないように指示します。使用例: extern&quot; C&quot; void printMe(int a);
関数がCから呼び出せるように関数のリンケージを変更します。実際には、関数名がマングル。
extern&quot; C&quot;でラップするだけでは、CヘッダーをC ++と互換性を持たせることはできません。 Cヘッダーの識別子がC ++キーワードと競合する場合、C ++コンパイラはこれについて文句を言います。
たとえば、g ++で次のコードが失敗するのを見ました:
extern "C" {
struct method {
int virtual;
};
}
Kindaは理にかなっていますが、CコードをC ++に移植する際に留意すべき点です。
CおよびC ++でコンパイルされた関数の名前はリンク段階で異なるため、リンク時にCスタイルでこれらの関数の名前を検索するようにC ++コンパイラに通知します。
extern&quot; C&quot; C ++コンパイラーによって認識され、指定された関数がCスタイルでコンパイルされている(またはコンパイルされる)ことをコンパイラーに通知するためのものです。リンク中に、Cから正しいバージョンの関数にリンクします。
「extern&quot; C&quot;」を使用しましたdll(ダイナミックリンクライブラリ)ファイルを作成する前など。main()関数&quot; exportable&quot;したがって、後で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);
}
extern&quot; C&quot;
は、 Cppソースファイルの C関数を呼び出すに使用されるリンケージ仕様です。 C関数を呼び出し、変数を書き込み、&amp;ヘッダーを含める。関数は外部エンティティ&amp;で宣言されています外部で定義されています。構文は
タイプ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)
{
//
//
}
この回答は、せっかちな人/期限に間に合うようにするためのものです。一部/簡単な説明のみを以下に示します:
- C ++では、オーバーロードを介してクラスに同じ名前を付けることができます(たとえば、すべて同じ名前はdllからそのままエクスポートできないなど)これらの問題の解決策は、異なる文字列に変換されることです(シンボルと呼ばれる)、シンボルは関数の名前と引数を説明するため、これらの各関数は同じ名前でも一意に識別できます(名前マングリングとも呼ばれます)
- Cでは、オーバーロードはありません。関数名は一意です(したがって、関数名を一意に識別するための別個の文字列は必要ないため、シンボルは関数名そのものです)
だから
C ++では、名前のマングリングが各関数を一意に識別します
Cでは、各関数を一意に識別している名前がなくても
C ++の動作を変更するには、つまり、特定の関数に対して名前のマングリングが発生しないように指定するには、 extern&quot; C&quot; をクライアントが使用するために、dllから特定の名前の関数をエクスポートするなど、何らかの理由で関数名。
他の回答を読んで、より詳細な/より正確な回答をご覧ください。
CとC ++を混合する(つまり、a。C ++からC関数を呼び出す、およびb。CからC ++関数を呼び出す)場合、C ++の名前のマングリングによりリンクの問題が発生します。技術的に言えば、この問題は、呼び出し先の関数が対応するコンパイラを使用して既にバイナリ(ほとんどの場合、*。aライブラリファイル)にコンパイルされている場合にのみ発生します。
したがって、extern&quot; C&quot;を使用する必要があります。 C ++で名前マングリングを無効にします。