extern "C"
をC++コードに正確に入れると何ができますか?
例えば:
extern "C" {
void foo();
}
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"を強調して)
まだ投稿されていないので、少し情報を追加したいと思いました。
あなたは非常に頻繁に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"
}
それは私が想像しているのとほぼ同じことです。
どちらが良いのかわからないが、私は両方を見たことがある。
すべてのC++プログラムで、すべての非静的関数はバイナリファイルでシンボルとして表されます。これらの記号は、プログラム内の機能を一意に識別する特別なテキスト文字列です。
Cでは、シンボル名は関数名と同じです。 Cでは、2つの非静的関数が同じ名前を持つことはできないため、これが可能です。
C++はオーバーロードを許可し、Cにはない多くの機能(クラス、メンバー関数、例外の仕様など)を備えているため、単に関数名をシンボル名として使用することはできません。これを解決するために、C++は、いわゆる名前マングリングを使用します。これは、関数名とすべての必要な情報(引数の数やサイズなど)を、コンパイラとリンカだけが処理する奇妙な文字列に変換します。
したがって、関数をextern 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()
結論:以下のシンボルタイプは両方とも{not}にマングルされていました。
Ndx = UND
)そのため、呼び出すときには両方ともextern "C"
が必要になります。
gcc
によって生成されたアンマングルドシンボルを期待するようにg++
に伝えます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との後方互換性を提供する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 a
とchar a
を持っていても)。
gcc -o printMe printMe.c && ./printMe;
1エラー。 PrintMeが複数回定義されています。
C++コンパイラは上記の例をコンパイルします。 printMe
が2回定義されていても構いません。
g ++ -o printMe printMe.c && ./printMe;
これは、C++コンパイラが暗黙的に( mangles )関数の名前をそのパラメータに基づいて)変更したためです。Cでは、この機能はサポートされていませんでした。同じ名前のメソッド(関数)で異なるクラスを作成し、異なるパラメータに基づいてメソッド( method overriding ))をオーバーライドする機能をサポートする必要があります。
しかし、include
sが他の従来のCファイルからの名前である、 "parent.c"という名前の従来のCファイル、 "parent.h"、 "child.h"などがあるとします。従来の "parent.c"ファイルが実行される場合C++コンパイラを使用すると、関数名は壊され、 "parent.h"、 "child.h"などで指定された関数名と一致しなくなります。そのため、これらの外部ファイル内の関数名も必要になります。こわされる。複雑なCプログラム(関数依存関係が多いもの)で関数名を乱すと、コードが壊れる可能性があります。そのため、関数名を乱さないようにC++コンパイラに指示できるキーワードを指定すると便利な場合があります。
extern
キーワードは、C++コンパイラに関数名を変換しないように指示します。使用例:extern void printMe(int a);
関数がCから呼び出せるように関数のリンケージを変更します。実際には、関数名は mangled ではありません。
Extern "C"をラップするだけでは、CヘッダーとC++の互換性を保つことはできません。 Cヘッダー内の識別子がC++キーワードと競合する場合、C++コンパイラはこれについて文句を言います。
例えば、私は次のコードがg ++で失敗するのを見ました:
extern "C" {
struct method {
int virtual;
};
}
Kindaは理にかなっていますが、CコードをC++に移植するときに留意する必要があります。
CとC++でコンパイルされた関数の名前はリンク段階で異なるため、リンク時にこれらの関数の名前をCスタイルで検索するようにC++コンパイラに通知します。
extern "C"は、C++コンパイラによって認識され、指定された関数がCスタイルでコンパイルされている(またはコンパイルされる)ことをコンパイラに通知することを意味します。そのため、リンクしている間は、Cから正しいバージョンの関数にリンクされます。
私は以前に '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);
}
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++の振る舞いを変更する、つまり、特定の関数に対して名前マングリングしてはいけないが起こるように指定するには、なんらかの理由で関数名の前にextern "C"を使用できます。そのクライアントが使用するために、DLLから特定の名前の関数をエクスポートするようなものです。
より詳細な/より正しい答えについては、他の答えを読んでください。
CとC ++とを混合するとき(すなわち、C ++からC関数を呼び出すこと;およびb.CからC ++関数を呼び出すこと)、C ++名前マングリングはリンク問題を引き起こす。技術的には、この問題は、呼び出し先の関数が対応するコンパイラを使用して既にバイナリ(ほとんどの場合、*。aライブラリファイル)にコンパイルされている場合にのみ発生します。
そのため、C++で名前マングリングを無効にするには、extern "C"を使用する必要があります。