extern "C"
C ++コードに入れると、正確にはどうなりますか?
例えば:
extern "C" {
void foo();
}
foo()
機能を備えたCppファイルがあることも意味します。
extern "C"
C ++コードに入れると、正確にはどうなりますか?
例えば:
extern "C" {
void foo();
}
foo()
機能を備えたCppファイルがあることも意味します。
回答:
extern "C"は、C ++の関数名に 'C'リンケージを持たせる(コンパイラは名前をマングルしない)ため、クライアントCコードは、関数の宣言。関数定義はバイナリ形式(C ++コンパイラーによってコンパイルされたもの)に含まれており、クライアントの「C」リンカーが「C」名を使用してリンクします。
C ++には関数名のオーバーロードがあり、Cにはないので、C ++コンパイラーは関数名をリンク先の一意のIDとして使用できないので、引数に関する情報を追加して名前をマングルします。Cで関数名をオーバーロードできないため、Cコンパイラーは名前をマングルする必要はありません。C++で関数にextern "C"リンケージがあると宣言した場合、C ++コンパイラーは、使用される名前に引数/パラメーター型情報を追加しません。リンケージ。
ご存知のように、個々の宣言/定義への "C"リンケージを明示的に指定するか、ブロックを使用して宣言/定義のシーケンスをグループ化し、特定のリンケージを持たせることができます。
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
専門性に関心がある場合は、C ++ 03標準のセクション7.5にリストされています。ここに簡単な要約を示します(extern "C"に重点を置いています)。
extern "C" { int i; }
は定義です。これは、の非定義の隣で、意図したものではない可能性がありますvoid g(char);
。これを非定義にするには、が必要になりますextern "C" { extern int i; }
。一方、中括弧のない1つの宣言の構文は、宣言を非定義にします。これextern "C" int i;
はextern "C" { extern int i; }
まだ投稿されていないので、ちょっと情報を追加したかっただけです。
Cヘッダーに次のようなコードが表示されることがよくあります。
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
これにより、マクロ "__cplusplus"が定義されるため、CヘッダーファイルをC ++コードで使用できるようになります。しかし、あなたはできるもまだマクロがされたレガシーCコードでそれを使用しないで、それが一意にC ++構文は表示されませんので、定義されました。
ただし、次のようなC ++コードも見ました。
extern "C" {
#include "legacy_C_header.h"
}
私が想像するのとほぼ同じことを達成します。
どちらの方法が良いかわかりませんが、両方を見ました。
extern "C"
、ヘッダーにあるため、符号化されていない名前も検索します。このテクニックを何度も使用して、うまくいきました。
extern "C"
は、ヘッダーが含まれる前または後に遭遇するかどうかを気にすることができませんでした。コンパイラに到達するまでには、いずれにしても、前処理されたテキストの1つの長いストリームにすぎません。
g++
でも、少なくとも過去17年間のどの時点においても、これは間違っていません。最初の例の要点は、CコンパイラとC ++コンパイラのどちらを使用するかは問題ではなく、extern "C"
ブロック内の名前に対して名前の変換は行われないということです。
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(); }
生成されたELF出力をコンパイルして逆アセンブルします。
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
出力には以下が含まれます。
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 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++
* because C does not know what this extern "C" thing is. */
#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 ++関数のオーバーロードを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でテスト済み。
extern "C" {
、あなたが呼び出すことができますC ++プログラム内からunmangled C関数をだけでなく、Cプログラム内からの関数++ unmangled C他の回答はそれほど明らかにしない、とあなたがの明確な例を示しているため)2各。ありがとう!
すべてのC ++プログラムでは、すべての非静的関数はバイナリファイルでシンボルとして表されます。これらの記号は、プログラム内の関数を一意に識別する特別なテキスト文字列です。
Cでは、シンボル名は関数名と同じです。これが可能なのは、Cでは2つの非静的関数が同じ名前を持つことができないためです。
C ++はオーバーロードを許可し、Cにはない多くの機能(クラス、メンバー関数、例外仕様など)を備えているため、単純に関数名をシンボル名として使用することはできません。これを解決するために、C ++は、関数名とすべての必要な情報(引数の数とサイズなど)を、コンパイラーとリンカーによってのみ処理される奇妙に見える文字列に変換する、いわゆる名前マングリングを使用します。
そのため、関数をextern Cとして指定すると、コンパイラーはそれを使用して名前のマングルを実行せず、シンボル名を関数名として使用して直接アクセスできます。
これは、そのような関数の使用中dlsym()
やdlopen()
呼び出し時に便利です。
ほとんどのプログラミング言語は、既存のプログラミング言語の上に構築されていません。C ++はCの上に構築されており、さらに手続き型プログラミング言語から構築されたオブジェクト指向プログラミング言語です。そのため、C extern "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;
}
同じ関数printMe
が2回定義されているため、Cコンパイラーは上記の例をコンパイルしません(たとえパラメーターint a
vs が異なっていてもchar a
)。
gcc -o printMe printMe.c && ./printMe;
1つのエラー。PrintMeが複数回定義されています。
C ++コンパイラは上記の例をコンパイルします。printMe
二度定義されても構いません。
g ++ -o printMe printMe.c && ./printMe;
これは、C ++コンパイラーがパラメーターに基づいて暗黙的に関数の名前を変更する(mangles)ためです。Cでは、この機能はサポートされていませんでした。ただし、C ++がC上に構築された場合、言語はオブジェクト指向になるように設計され、同じ名前のメソッド(関数)で異なるクラスを作成し、異なるクラスに基づいてメソッド(メソッドオーバーライド)をオーバーライドする機能をサポートする必要がありました。パラメーター。
extern "C"
「Cの関数名を壊さないでください」と言うただし、「parent.c」というinclude
名前のレガシーCファイルがあり、他のレガシーCファイルからの関数名である「parent.h」、「child.h」などがあるとします。レガシー「parent.c」ファイルが実行されている場合C ++コンパイラを使用すると、関数名はマングルされ、「parent.h」、「child.h」などで指定された関数名と一致しなくなります。したがって、これらの外部ファイルの関数名も必要になります。壊される。複雑なCプログラム(依存関係が多いプログラム)全体で関数名をマングルすると、コードが壊れる可能性があります。したがって、C ++コンパイラに関数名を壊さないように指示できるキーワードを提供すると便利な場合があります。
extern "C"
キーワードはありませんマングル(名前変更)Cの関数名にC ++コンパイラに指示します。
例えば:
extern "C" void printMe(int a);
extern "C"
、我々はちょうど持っている場合はdll
、ファイルを?つまり、ヘッダーファイルがなく、ソースファイル(実装のみ)があり、関数ポインターを介してその関数を使用している場合です。この状態では、(名前に関係なく)関数を使用しました。
extern "C"でラップするだけでは、CヘッダーをC ++と互換にすることはできません。Cヘッダー内の識別子がC ++キーワードと競合する場合、C ++コンパイラはこれについて文句を言います。
たとえば、次のコードがg ++で失敗するのを見ました。
extern "C" {
struct method {
int virtual;
};
}
ちょっと意味はありますが、CコードをC ++に移植するときに覚えておくべきことです。
extern "C"
他の回答で説明されているように、Cリンケージを使用することを意味します。「内容をCとしてコンパイルする」という意味ではありません。int virtual;
C ++では無効であり、別のリンケージを指定してもそれは変わりません。
これは、関数がCから呼び出せるように関数のリンケージを変更します。実際には、関数名はマングルされません。
undname
。
以前はdll(ダイナミックリンクライブラリ)ファイルなどに「extern "C"」を使用して、main()関数を「exportable」にしていたため、後で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 "C"
と__declspec(dllexport)
は無関係です。前者はシンボルの装飾を制御し、後者はエクスポートエントリの作成を担当します。C ++の名前の装飾を使用してシンボルをエクスポートすることもできます。この質問の要点を完全に逃しているだけでなく、コードサンプルにも他の誤りがあります。1つは、main
DLLからエクスポートしても戻り値が宣言されないことです。またはそのことについては、呼び出し規約。インポートするときは、ランダムな呼び出し規約(WINAPI
)を指定し、32ビットビルド(_main
または_main@0
)に間違ったシンボルを使用します。すみません、-1。
void*
いますが、実装は何も返しません。それは本当にうまく飛ぶでしょう...
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)
{
//
//
}
この回答は、せっかちな人/締め切りに間に合わせるためのもので、一部/簡単な説明は以下のとおりです。
したがって
、C ++では、名前をマングル化
してCの各関数を一意に識別します。名前をマングル化して各関数を一意に識別しなくても
C ++の動作を変更するには、つまり、特定の関数で名前のマングリングが発生しないように指定するには、dllから特定の名前の関数をエクスポートするなど、何らかの理由で関数名の前にextern "C"を使用できます。、そのクライアントが使用します。
より詳細な/より正しい答えについては、他の答えを読んでください。
他の良い答えと競合することなく、少し例を追加します。
C ++コンパイラが正確に行うこと:コンパイルプロセスで名前を壊すため、コンパイラに実装を特別に処理する ように指示する必要がありC
ます。
C ++クラスを作成してを追加するときextern "C"
、C呼び出し規約を使用していることをC ++コンパイラーに伝えます。
理由(C ++からC実装を呼び出す): C ++からC関数を呼び出すか、CからC ++関数を呼び出す(C ++クラスなどはCでは機能しません)。
Cコンパイラーによってコンパイルされた関数void f()と、C ++コンパイラーによってコンパイルされた同じ名前void f()を持つ関数は、同じ関数ではありません。その関数をCで記述し、C ++から呼び出そうとした場合、リンカーはC ++関数を検索し、C関数を見つけません。
extern "C"は、Cコンパイラによってコンパイルされた関数があることをC ++コンパイラに伝えます。Cコンパイラによってコンパイルされたことを伝えると、C ++コンパイラはそれを正しく呼び出す方法を認識します。
また、C ++コンパイラがC ++関数を呼び出すことができるようにC ++関数をコンパイルすることもできます。その関数は正式にはC関数ですが、C ++コンパイラーによってコンパイルされるため、すべてのC ++機能を使用でき、すべてのC ++キーワードを持っています。
extern "C"
関数をコンパイルできます—(いくつかの制約を条件として)Cコンパイラーによってコンパイルされたコードから呼び出すことができます。