extern "C"の意味

extern "C"の意味

Posted at July 4,2017 12:03 AM
Tag:[C/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プログラムとのリンケージが成功します。

関連記事
トラックバックURL


コメントする
greeting

*必須

*必須(非表示)


ご質問のコメントの回答については、内容あるいは多忙の場合、1週間以上かかる場合があります。また、すべてのご質問にはお答えできない可能性があります。予めご了承ください。

太字イタリックアンダーラインハイパーリンク引用
[サインインしない場合はここにCAPTCHAを表示します]

コメント投稿後にScript Errorや500エラーが表示された場合は、すぐに再送信せず、ブラウザの「戻る」ボタンで一旦エントリーのページに戻り(プレビュー画面で投稿した場合は、投稿内容をマウスコピーしてからエントリーのページに戻り)、ブラウザをリロードして投稿コメントが反映されていることを確認してください。

コメント欄に(X)HTMLタグやMTタグを記述される場合、「<」は「&lt;」、「>」は「&gt;」と入力してください。例えば「<$MTBlogURL$>」は「&lt;$MTBlogURL$&gt;」となります(全て半角文字)