extern "C"
前回は、C++ソースで定義された関数を
Cソース内でコールする場合に起こる問題について書きました。
そのキーワードが、「マングリング」でした。
前回:http://d.hatena.ne.jp/debian36/20080226
では、その問題を回避する方法、すなわちマングリングを避ける方法について、今日は記します。
ずばり、その答えが、extern "C" です。
この修飾子を見たことがあるかたも多いのではないでしょうか。
自分も、人のコードを見ていていると、たまに
extern "C"{
....
....
}
なんてコードが登場して、「ん〜、これは、C言語として解釈しろ」ってことなのかなぁってなんとなく思っていた程度でした。
確かに、extern "C" は、C言語として解釈して下さいよって、コンパイラ(g++)にお願いするものなのですが、なんのために、そのようなお願いをするのかというと、、、
「 マングリングを回避するため 」
なんですね。
ケース2:Cソースで定義されている関数をC++ソースから呼ぶ(前回の続き)
前回の例をそのまま使って実験してみます。
test.h
extern void hoge(void);
main.c
#include "./test.h" int main(void){ hoge(); return 0; }
このままビルドすると、前回示したように、
undefined reference error が起こります。
void hoge(void) 関数が、マングリングされたためでした。
では、この問題を回避するために、test.h を以下のように変更します。
test.h
#ifdef __cplusplus extern "C" void hoge(void); #else extern void hoge(void); #endif
この変更を加えることで、ビルドは成功するはずです。
このコードで本質的に行いたいことは、
関数 hoge をマングルしないようにすること
であり、それは、
関数 hoge を extern "C" で囲む
ということです。
ただ、extern "C" という修飾子は、Cコンパイラには解釈不能であるために、#ifdef ガードを加えています。
※ __cplusplus は C++ コンパイラ使用時に定義される識別子です。
test.hが、C++コードから include された場合は、
extern "C" void hoge(void);
が舐められ、 void hoge(void) という関数はこれ以降、extern "C" である関数として扱われます。これによって、 void hoge(void)は、マングルされず、Cからコール可能なシンボルに命名されます。
一方、test.hが、Cコードから include された場合は、
extern void hoge(void);
が舐められ、通常のプロトタイプ宣言として処理されます。
main.cpp
#include "./test.h" int main(void){ hoge(); return 0; }
この場合も結局は、マングルを回避すればよいはずですね。
main.cppの hoge() と関数をコールしている箇所を、C++コンパイラは、hoge(void)をマングルした結果のシンボル名をコールするように解釈してしまいます。
test.c はCコンパイラによって、_hoge のような素直なシンボルとして定義されているにもかかわらず、コールする側は、_Z4hogev などの複雑なシンボル名を期待してしまっています。
なので、コールする側が素直に _hoge を期待するように、
すなわちコールする側のシンボルをマングルしないようにします。
マングリング
今日の午後は、ずっとC++(g++)と闘っていました。。。
これまで C言語でコーディングされていたシステムの一部が、C++に変更されたたため、自分が担当する部分もそれに対応する必要がありました。
※ プロジェクト進行中に言語変更なんて自由やなぁ・・・(^^;
そんなん、簡単やろ〜、
C++って、C言語から派生したものやろ?
C++って、C上位互換やろ?
どうせ、バイトコードになっちゃえば C も C++ もいっしょやろ?
的に、思っていたのですが、問題の解決に午後丸々かかっちゃいました・・・
なんか身体的に疲れちゃいました。
組み込みなどの一部分野では、じわじわとC言語からC++言語への移行がはじまっている(? : 未確認情報)ので、
CとC++が混在するシステム(CとC++で1つの実行体)を作るケースがあるかもしれません。
そんな場合に役立つかも、ということで、今日学んだことをまとめます。
ケース1:まずは普通に Cソースで定義されている関数をCソースで呼ぶ
Makefile
今回の実験を通して使用するMakefile
.cファイルは gcc で
.cppファイルは g++ でコンパイルするようにしています。
( このように書かなくても、cppファイルを gcc に渡せば、g++ が呼ばれるようになっていたはずですが、はっきりしておくため。 )
TARGET = prg OBJS = test.o main.o all : $(OBJS) gcc -o $(TARGET) $(OBJS) clean : rm *.o *.exe .c.o : gcc -c $< .cpp.o : g++ -c $<
test.h
extern void hoge(void);
main.c
#include "./test.h" int main(void){ hoge(); return 0; }
実行結果
$ make gcc -c test.c gcc -c main.c gcc -o prg test.o main.c $ ./prg hoge!
これは普通に実行できますね。
ケース2:C++ソースで定義された関数をCソースで呼ぶ場合
ケース1で使用した、test.c を test.cpp にリネームします。
$ mv test.c test.cpp
で、再び make 実行!
※関係ない箇所は省略しています
$ make clean rm *.o *.exe $ make g++ -c test.cpp gcc -c main.c gcc -o prg test.o main.c ~/cc40ejpR.o : main.c : (.text+0x2b) : undefined reference to `_hoge' collect2: ld returned 1 exit status make: *** [all] Error 1
エラーです!!
コンパイル(オブジェクトファイルの作成)まではうまくいっているけど、
リンカでうまくいっていない、みたいです。
main.c : (.text+0x2b) : undefined reference to `_hoge'
のメッセージをみると、どうやら、main.c で呼ばれている、
関数 hoge()
が見つからない!と言っているようです。
なんで、ケース1では見つかった hoge() が、ケース2では見つからなかったのか?
その答えを導く言葉、それが「マングリング(Name Mangling)」です。
ケース1のオブジェクトファイルを覗いてみる
マングリングとは?の前に、まずは「うまくいった」ケース1にちょっと戻ります。
( そのために、test.cpp を test.c に戻しておきます )
$ mv test.cpp test.c $ make clean $ make gcc -c test.c gcc -c main.c gcc -o prg test.o main.c $ ./prg hoge!
やはり、うまく実行できるわけですが、ここで、ちょっとオブジェクトファイルを覗いてみます。
そこで使用するコマンドが、
nm コマンドです。
ManPage(http://www.linux.or.jp/JM/html/GNU_binutils/man1/nm.1.html)の説明によると、
nm - オブジェクトファイルのシンボルをリストする
とのこと。ソースファイルでは、あんな名前や、こんな名前で定義した変数・関数が、オブジェクトファイルではどんな名前(=シンボル)で外部に公開しているのかを知ることができるというものです。
では、さっそく、
$ nm test.o 00000000 b .bss 00000000 d .data 00000000 r .rdata 00000000 t .text 00000000 T _hoge U _printf
最後の2行しか理解できませんが :P
今回はそれで事足ります。
test.c では、
void hoge(void)
と定義した関数が、オブジェクトファイル内では、
_hoge というシンボル名で定義されているというですね。
この、_hoge どっかで見ましたね。
そう、ケース2のエラーメッセージ
main.c : (.text+0x2b) : undefined reference to `_hoge'
の箇所です。
_hoge というシンボルが見つからないといっているんですね。
では、ケース1では見つかった、シンボル _hoge が、どうしてケース2 では見つからないのか?
ケース2において、同じように nm コマンドを使ってみましょう。
$ mv test.c test.cpp $ make clean rm *.o *.exe $ make g++ -c test.cpp gcc -c main.c gcc -o prg test.o main.o main.o:main.c:(.text+0x2b): undefined reference to `_hoge' collect2: ld returned 1 exit status make: *** [all] Error 1 $ nm test.o 00000000 b .bss 00000000 d .data 00000000 r .rdata 00000000 t .text 00000000 T __Z4hogev U _printf
下から2行目に注目です。
ケース1では、_hoge というシンボルだったのが、
コンパイラを g++ にしたとたん、_Z4hogev とかいう、ごちゃごちゃしたシンボルになってます。
これがマングリングです。
C++では、関数のオーバーロード(引数だけを変えて宣言する方法)や、ネームスペースの概念があるため、C++のコンパイラである、g++は、このようにマングリングを行う必要があるのです。
一方の Cコンパイラは、このような概念を持たないため、関数 hoge()を呼んでいる箇所では、素直に _hoge を探します。
その場合に、_hoge シンボルが見つからないと、ケース2のようにエラーが起こってしまうわけです。
ということで、どうやら、C++ソースで定義された関数をCで呼ぶためには、C++ソースからオブジェクトファイルを作成する際に、この「マングリング」を行わないようにすればよさそうです。
その話は、また次回。。
すぐ書いちゃうかもですが・・・
SystemV メッセージキュー
前回に続き、メッセージキューを用いたIPCプログラミング。
コマンドライン引数で受けた文字列をキューに追加するプログラムと、
それを取り出すプログラム。
2つの独立したプログラム(プロセス)が、ひとつのメッセージキューを通して、コミュニケーションしています。
defs.h
ftokに指定するpath nameの定義
メッセージ型の定義
#define QPATH "/tmp/msgq0225" typedef struct{ long mtype; char str[100]; }MSG;
enq.c
#include// ftok() #include // key_t #include // msg***() #include // O_CREATE #include // strcpy() #include "./defs.h" // QPATH, MSG /* コマンドライン引数に与えられた、文字列を * メッセージキューに追加する */ int main(int argc, char** argv){ MSG obj = { 1 }; int qid; if(argc != 2){ // error return 0; } // メッセージの準備 strcpy(obj.str, argv[1]); // メッセージキュー識別子の取得 qid = msgget(ftok(QPATH,1), O_CREAT | 0666); // メッセージキューへの追加 msgsnd(qid, &obj, sizeof(MSG)-sizeof(long), 0); return 0; }
deq.c
#include// ftok() #include // key_t #include // msg***() #include // O_CREATE #include //printf() #include "./defs.h" // QPATH, MSG /* メッセージキューからメッセージを取り出す */ int main(void){ MSG obj; int qid = msgget(ftok(QPATH,1), O_CREAT | 0666 ); // メッセージキューから取り出す msgrcv(qid, &obj, sizeof(MSG)-sizeof(long), 0, 0); printf("Received Message : %s\n",obj.str); return 0; }
実行結果
三回目の ./deq 実行では、もうメッセージキューにメッセージがないため、ブロックされます。
※ msgrcvの第5引数にIPC_NOWAITをしていすると、命令は即座に返ります。
$ ./enq Debian $ ./enq RedHat $ ./deq Received Message : Debian $ ./deq Received Message : RedHat $ ./deq
ftok( )によって得られる、key_tに関連付けられたメッセージキューがなければ
新規作成、なければ既存キューの識別子を返すことを意味する IPC_CREAT。
毎回、 IPC_CREATE と書いて、gcc に怒られます。。
POSIX Semaphore
これも、Cygwinでやろうとしたんだけれど、POSIXリアルタイムライブラリが見つからなかったため、断念。
で、でびあん♪
defs.h
セマフォ名(openするときの素(もと)となるもの)の定義のみ。
はじめ、これを/tmp/sem123 としてしまって、sem_openで、0が返ってきてしまっていた。
openに指定するnameは、
・スラッシュではじまり
・スラッシュはひとつのみ
・そのファイルは実存しなくてよい
#define SEMPATH "/sem123"
wait.c
セマフォの作成とセマフォ待ち(P操作)を行う
既に同じセマフォがあると気持ち悪いので、まずは sem_unlinkで削除し、
次のsem_open内で、
O_CREATを指定し、新規に作成することを決定的にする。
O_RDWRは、今回のアクセスは読み書きモードでオープンすることを示す。
この,O_CREAT,ORDWRの定義を参照するために、fcntl.hをインクルード。
S_IRWXU(0)は、ここで作成するセマフォのアクセス許可を指定している。
(ユーザ、他人の読み書き実行許可)
これは、
#include#include #include #include #include "defs.h" int main(void){ sem_t *semID; sem_unlink(SEMPATH); semID = sem_open(SEMPATH, O_CREAT|O_RDWR, S_IRWXU|S_IRWXO, 1); printf("%d\n",semID); for(;;){ puts("sem_wait...\n"); sem_wait(semID); puts("sem_wait SUCCESS\n); } return 0; }
post.c
セマフォのポスト(V操作)を行う
#include#include #include #include "defs.h" int main(void){ sem_t* semID = sem_open(SEMPATH, O_RDWR); for(;;){ puts("sem_post!!\n"); sem_post(semID); } return 0; }
コンパイル時には、リアルタイムライブラリを読み込むため、gccの場合、 -lrt と指定する。
./wait& ./post
などと実行すると、
waitのメッセージと
postのメッセージが交互に延々と出力されるはずです。
Cygwinでメッセージキュー使用
今日は、でびあんはお休み。
その代わりに、Cygwinで IPCプログラミングを試みる。
メッセージをSendするプログラムと
メッセージをRecvするプログラムの簡単なもの。
def.h
メッセージキューキー取得のための、ftokに取得するパスの定義、
メッセージキューに貯めるメッセージ型の定義。
第一メンバは、メッセージタイプを示す long型である必要あり。
#define QPATH "/tmp/msgq123" typedef struct{ long mtype; char str[100]; }MSG;
producer.c
メッセージキューの作成、
メッセージの追加を行う。
msgget時に IPC_CREAT | 0666 を指定することで、
既存のキューがない場合、アクセス許可レベルが0666( 全員読み書きOK )であるキューを新規作成する。
#include#include #include #include #include "def.h" int main(void){ int qid = msgget(ftok(QPATH,1), IPC_CREAT | 0666); MSG obj = { 1, "Saturday end" }; puts("Message Send..."); msgsnd(qid, &obj, sizeof(MSG)-sizeof(long), 0); strcpy(obj.str, "Sunday Start!!"); puts("Message Send..."); msgsnd(qid, &obj, sizeof(MSG)-sizeof(long), 0); return 0; }
consumer.c
メッセージの取得を行う。
このプログラム実行前に、キューの作成が行われていることが前提。
※ msggetに IPC_CREATを指定していないため。
#include#include #include #include #include "def.h" int main(void){ MSG obj; int qid = msgget( ftok(QPATH,1), 0 ); for(;;){ puts("Message Receiving..."); msgrcv(qid, &obj, sizeof(MSG)-sizeof(long), 0, 0); printf("Received Msg : %s\n", obj.str); } return 0; }
で、これを Cygwinで実行するためには、
環境変数 CYGWIN に、 server を指定し、
あらかじめ、 /usr/sbin/cygserver.exe を実行しておく必要があります。
※メッセージキューの概念は、単独のプログラムにとどまるものでなく、なんらかのバックグラウンドで動作するプログラムが必要であるため。
これをやっておかないと、
Bad system call エラーとなります。
ちなみに、今回のプログラムで、
char str[100] = "Hello";
ができても、
str = "Hello";
ができないことに気づきました・・・
いまさら・・・(--;
参考にさせていただいたサイト:
http://asistobe851.hp.infoseek.co.jp/my-memo/mq.html
http://www.sixnine.net/cygwin/translation/cygwin-ug-net/using-cygwinenv.html
データ型の定義場所
あるヘッダファイル( hoge.h )で、構造体をたくさん定義していた。
ただ、なにか違和感があった。
そのヘッダファイルをインクルードする他のソース( src1.c src2.c )で、それらの構造体を使っているわけではない。
どこで使っているかというと、そのヘッダファイルの実装( hoge.c )のみ。
それならば、hoge.c でそれらの構造体を定義すればいいのでは?と。
無駄にヘッダファイルとして外部に公開するよりも、外部では使われないということを明らかにするために、
内部的に定義するのが安心なプログラム構造だと思う。
ヘッダで構造体型を定義するのは、
その実装部が責務を果たすため(機能を全うするため)に外部から与えてほしいデータ群がある場合だろう。
A.cで実現する機能 funcA を実装(実現)するためには、どうしても呼ばれる側(ユーザ)から data1,data2,data3...
がいるという場合に、それらの data をまとめて、ひとつの structA としてヘッダファイルにおいて定義し、funcAのユーザにその構造体を用意してもらい、そのオブジェクトを func1 の引数としてうけるというのがヘッダファイルにおいて構造体型を定義する理由、かなと。
これこれこういうデータ型オブジェクトが欲しいっていうなら自分で用意しなさいということ。
で、自分以外にそのデータ型を使わないなら、無駄に公開しないほうがいいよっていうこと。
非機能要求
友人から、彼が開発中のシステムにおいて、仕様の変更が発生しその影響がかなり大きく大変だという話を聞いた。
アーキテクチャは、オブジェクト指向に基づいて設計されていることは知っていたから、
ついつい、「え、せっかくオブジェクト指向で設計、実装してるのに、そんな影響が広がってしまうの?」
と言ってしまった。
変更された仕様がどのくらい重要なものかはわからないが、
おそらく、変更される予定は当初の(非機能)要求には含まれていなかったのだろう。
だから、その部分の変更については、変更の容易性を考慮した設計は行われなかったのだろうと後になって考えた。
あらゆる変更に対処しうる設計を目指していたら、どんなに工数割いても終わらないですよね。。。
オブジェクト指向で開発すれば、なんでも再利用性、変更容易性が高まるわけではまったくなくて、
要求として、「この部分についての再利用性、この機能についての変更容易性」を定めた上で、
それらを高める ことができる というものだ。