2015/11/23

[c/c++]extern "C"の復習

あまりやらないけど、extern "C"するときがある。
なんとなく、extern "C"で囲みすぎているような気がするので、復習しておこう。


なにもなし

  1: //main.c
  2: #include "test.h"
  3: int main()
  4: {
  5:     func_test(10);
  6: 
  7:     return 0;
  8: }
  1: //test.h
  2: #ifndef TEST_H__
  3: #define TEST_H__
  4: void func_test(int a);
  5: #endif /* TEST_H__ */
  1: //test.c
  2: #include <stdio.h>
  3: void func_test(int a)
  4: {
  5:     printf("%s(%d)\n", __func__, a);
  6: }

$ g++ -c main.cpp
$ g++ -c test.c
$ g++ -o tst test.o main.o
$ nm test.o
00000000 T __Z9func_testi
00000008 r __ZZ9func_testiE8__func__
$ nm main.o
         U __Z9func_testi

gccは、特に拡張子を見分けたりはしないようだ。


test.hにextern "C"

  1: //test.h
  2: #ifndef TEST_H__
  3: #define TEST_H__
  4: extern "C" {
  5: void func_test(int a);
  6: }
  7: #endif /* TEST_H__ */

$ g++ -o tst test.o main.o
main.o:main.cpp:(.text+0x16): `func_test' に対する定義されていない参照です
$ nm test.o
00000000 T __Z9func_testi
00000008 r __ZZ9func_testiE8__func__
$ nm.main.o
         U _func_test

結局のところ、extern "C"はオブジェクト生成するときの指示になる。
だから、test.hをincludeしているmain.cppは「func_test()はC系の名前なんだ」と思ってオブジェクトを作るけど、test.cはtest.hをincludeしていないので、コンパイラがg++だからC++系の名前でオブジェクトを作る。
だから、リンクしようとしても名前が合わなくて、エラーになる。


じゃあ、両方つける?

  1: //test.c
  2: #include <stdio.h>
  3: extern "C" {
  4: void func_test(int a)
  5: {
  6:     printf("%s(%d)\n", __func__, a);
  7: }
  8: }

これは、うまくいく。
いくけどさぁ。。。


単に、test.cの行儀が悪いだけ、という気もするが、もうちょっと考えてみよう。

まず、staticな関数じゃないのに、ヘッダファイルに関数定義を作らないのはどういうときか?
思いつくパターンとしては、

  • ざざっと急いで作ったので、1つのソースファイルにまぜこぜした機能が入ってしまった
  • なんか気持ち悪いので、とりあえずソースファイルを小分けすることにした
  • 小分けすると、共通で使うわけではないが、機能的には別ファイルに置いた方がよい関数が出てきた
  • うーん・・・
  • ヘッダファイルには書かずに、使うソースがexternすることにしてしまえ!

といった感じだろうか?
弱い非公開関数、みたいなイメージだ。

「そんなのするべきじゃない」というのは簡単なのだけど、まあ、ここではそういう話はしない。
そういうパターンがあるかも、くらいにしておく。

 

そもそも、関数を参照する方はプロトタイプ宣言みたいに前方参照するものがいるけど、定義しているところは自分が本物だから何も言われないのだよなぁ。
もし、関数定義しているところも宣言を読まないといけないのだったら、「ヘッダに書きましょう」で終わるんだけどねぇ。
やるんだったら、内部参照用のヘッダファイルを別に定義して、そっちをソースファイルはincludeするようにするのかな。

そういうわけで、私の場合はこうしておこう。

  • 公開することになる関数のプロトタイプ宣言は、必ず書く
  • 公開することになるプロトタイプ宣言は、ヘッダファイルに書く
  • 公開範囲を制限したいときは、ヘッダファイルを分割する
  • 非公開関数のプロトタイプ宣言は、そのときの気分で
    • お仕事用の時は、ちゃんとするよ!
  • ソースファイルをincludeしたりする人は、もう知らん
    • 単体テストの時とか、めんどくさくてやったりするけど、例外ってことで

 

まあ、今回のtest.cでincludeを書いていないのは、本当に書き忘れていただけなんだけどね・・・。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。