マングリング

今日の午後は、ずっと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);

test.c

#include 
#include "./test.h"

void hoge(void){
	printf("hoge!\n");
}

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++ソースからオブジェクトファイルを作成する際に、この「マングリング」を行わないようにすればよさそうです。


その話は、また次回。。
すぐ書いちゃうかもですが・・・