extern "C"の意味
extern "C"の意味について調べてみました。
1.はじめに
CプログラムからC++の関数を起動することを確認するため、下記のサンプルを作りました。
foo.h
class Foo {
private:
int number;
public:
Foo(int num);
int print(void);
};
void construct();
void foo_print();
void destruct();
foo.cpp
#include "foo.h"
#include <iostream>
Foo::Foo(int num) {
number = num;
}
int Foo::print(void) {
std::cout << number << '\n';
}
Foo *foo;
void construct() {
foo = new Foo(100) ;
}
void foo_print() {
foo->print();
}
void destruct() {
delete(foo);
}
test.c
extern foo_print();
extern construct();
extern destruct();
int main() {
construct();
foo_print();
destruct();
}
このサンプルをビルドすると、リンケージで下記のエラーが発生します。
% gcc -c test.c
% g++ -c foo.cpp
% g++ -o a.out test.o foo.o
test.o: 関数 `main' 内:
test.c:(.text+0xa): `construct' に対する定義されていない参照です
test.c:(.text+0x14): `foo_print' に対する定義されていない参照です
test.c:(.text+0x1e): `destruct' に対する定義されていない参照です
collect2: エラー: ld はステータス 1 で終了しました
2.extern "C"の意味
リンケージエラーの説明の前に、サンプルコードfoo.cppをコンパイルのみ実行し、nmコマンドでシンボルリストを表示してみます。
% g++ -c foo.cpp
% nm foo.o
00000000000000db t _GLOBAL__sub_I__ZN3FooC2Ei
000000000000009e t _Z41__static_initialization_and_destruction_0ii
0000000000000089 T _Z8destructv
0000000000000043 T _Z9constructv
0000000000000074 T _Z9foo_printv
0000000000000008 b _ZL3foo
0000000000000016 T _ZN3Foo5printEv
0000000000000000 T _ZN3FooC1Ei
0000000000000000 T _ZN3FooC2Ei
U _ZNSolsEi
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
U _ZdlPv
U _Znwm
U __cxa_atexit
U __dso_handle
次にfoo.hを下記のように「extern "C"」を付与してコンパイルし、nmコマンドでシンボルリストを表示してみます。
foo.h
#ifdef __cplusplus
extern "C" {
#endif
class Foo {
private:
int number;
public:
Foo(int num);
int print(void);
};
void construct();
void foo_print();
void destruct();
#ifdef __cplusplus
}
#endif
% g++ -c foo.cpp
% nm foo.o
00000000000000db t _GLOBAL__sub_I__ZN3FooC2Ei
000000000000009e t _Z41__static_initialization_and_destruction_0ii
0000000000000008 b _ZL3foo
0000000000000016 T _ZN3Foo5printEv
0000000000000000 T _ZN3FooC1Ei
0000000000000000 T _ZN3FooC2Ei
U _ZNSolsEi
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
U _ZdlPv
U _Znwm
U __cxa_atexit
U __dso_handle
0000000000000043 T construct
0000000000000089 T destruct
0000000000000074 T foo_print
2つのシンボルリストの違いは下記です。
extern "C"なし
0000000000000089 T _Z8destructv
0000000000000043 T _Z9constructv
0000000000000074 T _Z9foo_printv
extern "C"あり
0000000000000043 T construct
0000000000000089 T destruct
0000000000000074 T foo_print
extern "C"なしのシンボルは"_Z8"や"_Z9"という文字が追加されています。
これは「マングリング」と呼ばれ、C++では関数のオーバーロードや、複数のネームスペースに存在する同名の変数を見分けるようにするため、このようなマングリングを行う必要があるようです。
が、C言語にはマングリングという概念はありません。
test.cのオブジェクトファイルのシンボルは下記のようになっています。
% nm test.o
U construct
U destruct
U foo_print
0000000000000000 T main
つまり「extern "C"」なしでコンパイルを行うと、リンケージでCプログラムが期待するシンボル名construct/destruct/foo_printがみつからず、冒頭のリンケージエラーになってしまいます。
「extern "C"」ありでコンパイルを行うと、マングリングが不要なもの(ここではconstruct/destruct/foo_print)はマングリングされないため、Cプログラムとのリンケージが成功します。
Posted by yujiro このページの先頭に戻る
トラックバックURL
コメントする
greeting