読者です 読者をやめる 読者になる 読者になる

extern "C"

C++

C言語C++が混在するプロジェクトでは、よく「extern "C"」という記述が見られる・・・。

これは、C言語のソースからC++の関数、またはその逆を行う時に起こるマングリングという問題を回避するための記述である・・・。
(詳細は、
マングリング - debian36の日記
extern "C" - debian36の日記
を参照のこと)

言葉では説明しにくいので、サンプルと合わせて解説・・・。

まずは、C++からC言語の関数を呼ぶ場合・・・。

main_1.cppが、

// ヘッダファイルのインクルード
#include "extern_c_1.h" // extern_c_1

// main関数の定義
int main(){

  // extern_c_1.cの関数を呼ぶ.
  extern_c_1_func(); // extern_c_1_funcを呼ぶ.

  // プログラムの終了
  return 0;

}

こうだとする・・・。
externc_1_funcは、extern_c_1.hで

と宣言され、

extern_c_1.cで、

と定義された関数である・・・。つまりC言語の関数・・・。

extern_c_1.cをC言語なのでgccコンパイル・・・。

$ gcc -c extern_c_1.c

すると、extern_c_1.oというオブジェクトファイルが生成される・・・。

main_1.cppはC++なのでg++でコンパイル・・・。

$ g++ -c main_1.cpp

すると、main_1.oが生成される・・・。

さて、2つのオブジェクトファイルができたので、実行ファイルを作るにはこれらをリンクしないといけない・・・。

$ g++ -o extern_c_1 extern_c_1.o main_1.o
main_1.o: 関数 `main' 内:
main_1.cpp:(.text+0x5): `extern_c_1_func()' に対する定義されていない参照です
collect2: エラー: ld はステータス 1 で終了しました
$

リンクすると、このようなエラーが出てしまう・・・。
extern_c_1_func()という関数が見つからないのである・・・。

nmコマンドで、オブジェクトファイルのシンボルがどうなっているか見てみる・・・。
Man page of nm

extern_c_1.oは、

$ nm extern_c_1.o
0000000000000000 T extern_c_1_func
                 U puts
$

extern_c_1_funcというシンボルは存在している・・・。
一方、main_1.oは、

$ nm main_1.o
                 U _Z15extern_c_1_funcv
0000000000000000 T main
$

_Z15extern_c_1_funcvという先頭や末尾になんらかの文字列が付いたシンボルになっている・・・。これがマングリングである・・・。
C++の場合、同じ関数名でもオーバーロードなどで違う関数と区別する必要があるため、シンボルが関数名と同一にはならないのである・・・。
しかし、これでは同じ関数としてリンクできない・・・。
そこで「この部分はC言語として解釈する」という記述が「extern "C"」である・・・。

とすると、"{"と"}"で囲まれた部分をC言語とみなし、ヘッダファイルに宣言されているextern_c_1_funcのシンボルをつくるときに、C言語流の余計な文字列をつけないものにしてくれる・・・。

これでオブジェクトファイルを生成して、nmコマンドで見ると、

$ g++ -c main_1.cpp
$ nm main_1.o
                 U extern_c_1_func
0000000000000000 T main
$

シンボルがextern_c_1_funcになった・・・。

これでリンクができるようになり、

$ g++ -o extern_c_1 extern_c_1.o main_1.o
$ ./extern_c_1
extern_c_1_func!
$

ちゃんと、C言語側の関数が呼べる・・・。

今度は、C言語からC++の関数を呼ぶ場合・・・。

extern_c_2.cppを、

// ヘッダファイルのインクルード
#include <cstdio> // 標準入出力
#include "extern_c_2.h" // extern_c_2

// extern_c_2_funcの定義
void extern_c_2_func(){ // extern_c_2.cppの関数

  // extern_c_2_funcが呼ばれたことを出力.
  printf("extern_c_2_func!\n"); // "extern_c_2_func!"

}

こう書いて、オブジェクトファイルを生成すると、

$ g++ -c extern_c_2.cpp
$ nm extern_c_2.o
0000000000000000 T _Z15extern_c_2_funcv
                 U puts
$

こうなってしまう・・・。

これでコンパイルすれば、

$ g++ -c extern_c_2.cpp
$ nm extern_c_2.o
0000000000000000 T extern_c_2_func
                 U puts
$

C言語流に・・・。

これで、

$ gcc -o extern_c_2 extern_c_2.o main_2.o
$ ./extern_c_2
extern_c_2_func!
$

C言語からC++側の関数を呼べる・・・。

Sample/main_1.cpp at master · bg1bgst333/Sample · GitHub
Sample/extern_c_1.h at master · bg1bgst333/Sample · GitHub
Sample/extern_c_1.c at master · bg1bgst333/Sample · GitHub
Sample/main_2.c at master · bg1bgst333/Sample · GitHub
Sample/extern_c_2.h at master · bg1bgst333/Sample · GitHub
Sample/extern_c_2.cpp at master · bg1bgst333/Sample · GitHub