マングリング
今日の午後は、ずっと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++ソースからオブジェクトファイルを作成する際に、この「マングリング」を行わないようにすればよさそうです。
その話は、また次回。。
すぐ書いちゃうかもですが・・・