バイナリ形式のシンボルとC++のマングル
シンボルがバイナリ形式の中に
コンピュータ上で動くプログラムは最終的には何らかの形でバイナリの実行形式になっています。
最近はスクリプト言語や、統合開発環境が広く使われるようになっているため、あまりこのあたりを意識することはないですね。
あまり意識しなくてもいいわけですが、バイナリについての知識をもっていると、普通では思いつかないソリューションを思いつくことができます。
「バイナリを制するものは世界を制す」です。
今回はバイナリ形式に存在するシンボルについて紹介します。
プログラムは実行形式単独で動くわけではなく、OSや他のツールが提供するライブラリを利用しながら動作します。
他のライブラリとの連携には何らかの仕組が必要になるわけですが、そこでシンボルが使われるわけです。
平たくいうと、シンボルとは識別するための「名前」ですね。
C言語が生成するシンボルはシンプル
まずはC言語で簡単なコードをコンパイルしてシンボルがどうなっているのかを見てみましょう。
foo.cとして次のコードを保存します。
void foo() { }
このコードをコンパイルしてオブジェクトファイルに変換します。
$ gcc -c foo.c
$ ls foo.o
foo.o
foo.oが生成されていることがわかります。
次にnmコマンドを使ってシンボルを表示してみます。
$ nm foo.o
0000000000000000 T foo
先ほど定義した関数fooがシンボルとして現れていることがわかります。
C++のシンボルはマングルされている
今度はC++のが生成するシンボルについて見てみましょう。
foo.ccとして次のコードを保存します。
void foo() { }
先ほどと同様にコンパイルしてオブジェクトファイルに変換します。
$ g++ -c foo.cc
$ nm foo.o
0000000000000000 T _Z3foov
U __gxx_personality_v0
先ほどのC言語の場合とは違い、_Z3foovというシンボルが出てきました。
これはマングルされたシンボルと呼ばれます。
C++言語ではクラスや関数のオーバーロードがあるため、単純に関数名はメソッド名をシンボルにすると、 同じ関数名やメソッド名のシンボルが衝突してしまい、シンボルで一意に関数やメソッドを識別できなくなります。
このような事態を回避するために、シンボルがマングルされるわけです。
c++filtを使えば、マングルされたシンボルから元の関数宣言を復元することができます。
$ c++filt _Z3foov
foo()
また、次のように'extern "C"'で宣言するとC言語のようにマングルされていない関数名としてシンボルが生成されます。
$ echo 'extern "C" void foo() {}' > foo.cc
$ g++ -c foo.cc
$ nm foo.o
U __gxx_personality_v0
0000000000000000 T foo
まとめ
今回、シンボルとC言語やC++でのシンボルを見ていきました。
シンボルを理解するとバイナリについて少し親近感が沸くのではないでしょうか。
ご質問等、何かありましたらこちらへ。