C++/名前修飾
はじめに
[編集]名前修飾( name mangling )とは、関数名やクラス名などのシンボルに、その型情報や呼び出し規約などの情報をエンコードする仕組みです。これは、C++の多重オーバーロードやテンプレートなどの機能をサポートするために必要不可欠なものです。
歴史的背景
[編集]最初のC++コンパイラは、C言語のソースコードを翻訳するコンパイラとして実装されました。そのため、シンボルの名前はC言語の識別子の規則に従う必要がありました。しかし、C++が発展していくにつれて、多重オーバーロードやテンプレートなどの機能が導入されました。これらの機能は、同じ名前を持つ複数のシンボルを定義することを可能にするものでしたが、C言語の識別子規則では区別することができませんでした。
この問題を解決するために、C++コンパイラは名前修飾と呼ばれる仕組みを導入しました。名前修飾は、シンボル名にその型情報や呼び出し規約などの情報をエンコードすることで、異なるシンボルを区別できるようにします。
単純な例
[編集]名前修飾の仕組みを理解するために、簡単な例を考えてみましょう。次のコードは、2つの整数を受け取ってその和を返す2つの関数 add
を定義しています。
$ cat mangle1.cc auto add(int x, int y) -> int { return x + y; } auto add(double x, double y) -> double { return x + y; } $ clang++ -c mangle1.cc $ llvm-nm mangle1.o 0000000000000020 T _Z3adddd 0000000000000000 T _Z3addii
この例では、まずmangle1.cc
というファイルに、auto add(int x, int y) -> int
関数と auto add(double x, double y) -> double
関数が定義されています。
次に、コンパイルと名前修飾の確認が行われます。clang++
を使用して mangle1.cc
をコンパイルし、生成されたオブジェクトファイル mangle1.o
を llvm-nm
を使用して調べます。
llvm-nm
の出力結果には、それぞれの関数の名前修飾されたシンボルが表示されます。auto add(int x, int y) -> int
関数と auto add(double x, double y) -> double
関数の名前修飾されたシンボルがそれぞれ Z3addii
と _Z3adddd
です。@@@
- 関数
auto add(int x, int y) -> int
の名前修飾_Z3addii
_Z
: C++の名前修飾接頭辞(Itanium C++ ABIを使用)3add
: 関数名add
。3
は関数名の長さii
: 関数の引数の型。i
はint
型を表す。ここでは2つのint
型の引数を表すため、ii
となる
- 関数
auto add(double x, double y) -> double
の名前修飾_Z3adddd
_Z
: C++の名前修飾接頭辞(Itanium C++ ABIを使用)3add
: 関数名add
。3
は関数名の長さdd
: 関数の引数の型。d
はdouble
型を表す。ここでは2つのdouble
型の引数を表すため、dd
となる
これらの名前修飾により、同じ名前の関数であっても、異なる引数の型に基づいて一意に識別できるようになります。名前修飾は、コンパイラが異なる関数オーバーロードやテンプレートのインスタンスを正しく区別するために使用されます。
nm
、llvm-nm
、および gcc-nm
nm
、llvm-nm
、および gcc-nm
は、それぞれ異なるコンパイラツールチェーンで使用されるコマンドラインツールであり、コンパイルされたオブジェクトファイルやライブラリの中に含まれるシンボル情報を表示するために使用されます。以下にそれぞれのツールについての詳細を示します。
- nm
nm
は、UNIX系のシステムで広く使われるツールで、オブジェクトファイル、ライブラリ、実行可能ファイルに含まれるシンボルテーブルを表示します。シンボルテーブルには、関数や変数の名前、アドレス、セクション情報などが含まれます。- 主な機能
- シンボルのリスト表示
- 各シンボルの型やアドレスの表示
- グローバルシンボルやローカルシンボルの区別
- 例
$ nm mangle1.o 0000000000000020 T _Z3adddd 0000000000000000 T _Z3addii
- llvm-nm
llvm-nm
は、LLVMプロジェクトの一部として提供されるnm
のバージョンです。llvm-nm
は、特にLLVMコンパイラツールチェーン(Clangなど)と一緒に使われます。基本的な機能はnm
と同じですが、LLVM特有の機能やフォーマットにも対応しています。- 主な機能
- シンボルのリスト表示
- 各シンボルの型やアドレスの表示
- LLVM特有のバイナリフォーマットのサポート
- 例
$ llvm-nm mangle1.o 0000000000000020 T _Z3adddd 0000000000000000 T _Z3addii
- gcc-nm
gcc-nm
は、GNUコンパイラコレクション(GCC)に含まれるnm
のバージョンです。通常のnm
コマンドと同様に、オブジェクトファイルやライブラリのシンボルテーブルを表示しますが、GCCツールチェーンと密接に統合されています。- 主な機能
- シンボルのリスト表示
- 各シンボルの型やアドレスの表示
- GCC特有のオプションや機能のサポート
- 例
$ gcc-nm mangle1.o 0000000000000020 T _Z3adddd 0000000000000000 T _Z3addii
- 使用例と説明
- 以下は、テンプレート関数
add
と通常関数sadd
の名前修飾に関する具体的な例と解説です。 // mangle1.cc auto add(int x, int y) -> int { return x + y; } auto add(double x, double y) -> double { return x + y; }
$ clang++ -c mangle1.cc $ llvm-nm mangle1.o 0000000000000020 T _Z3adddd 0000000000000000 T _Z3addii $ c++filt -n _Z3adddd add(double, double) $ c++filt -n _Z3addii add(int, int)
- 関数
add(int, int)
の名前修飾_Z3addii
_Z
: C++の名前修飾接頭辞(Itanium C++ ABIを使用)3add
: 関数名add
。3
は関数名の長さii
: 関数の引数の型。i
はint
型を表す。ここでは2つのint
型の引数を表すため、ii
となる
- 関数
add(double, double)
の名前修飾_Z3adddd
_Z
: C++の名前修飾接頭辞(Itanium C++ ABIを使用)3add
: 関数名add
。3
は関数名の長さdd
: 関数の引数の型。d
はdouble
型を表す。ここでは2つのdouble
型の引数を表すため、dd
となる
複雑な例
[編集]名前修飾は、より複雑な関数やクラスにも適用できます。例えば、次のコードは、テンプレート化された add
関数を定義しています。
$ cat mangle2.cc template <typename T> auto add(T x, T y) -> T { return x + y; } short sadd(short x, short y) { return add(x, y); } $ clang++ -c mangle2.cc $ llvm-nm mangle2.o 0000000000000000 W _Z3addIsET_S0_S0_ 0000000000000000 T _Z4saddss
この例では、まずmangle2.cc
というファイルに、テンプレート関数 add
と通常の関数 sadd
が定義されています。add
関数は2つの引数を加算して返すテンプレート関数であり、sadd
関数は2つの short
型の引数を add
関数を介して加算して返す関数です。
次に、コンパイルと名前修飾の確認が行われます。clang++
を使用して mangle2.cc
をコンパイルし、生成されたオブジェクトファイル mangle2.o
を llvm-nm
を使用して調べます。
llvm-nm
の出力結果には、それぞれの関数の名前修飾されたシンボルが表示されます。add
関数と sadd
関数の名前修飾されたシンボルがそれぞれ _Z3addIsET_S0_S0_
と _Z4saddss
です。
- テンプレート関数
add<short>(short, short)
の名前修飾_Z3addIsET_S0_S0_
_Z
: C++の名前修飾接頭辞(Itanium C++ ABIを使用)3add
: 関数名add
。3
は関数名の長さIsE
: テンプレート引数short
。I
はテンプレート引数の始まりを示し、s
はshort
型、E
はテンプレート引数の終わりを示すT_S0_S0_
: 関数の引数の型。T_
はテンプレート引数に基づく戻り値の型、S0_
はテンプレートの第1引数 (short
) を表し、もう一つのS0_
は第2引数 (short
) を表す
- 通常の関数
sadd(short, short)
の名前修飾_Z4saddss
_Z
: C++の名前修飾接頭辞(Itanium C++ ABIを使用)4sadd
: 関数名sadd
。4
は関数名の長さss
: 関数の引数の型。s
はshort
型を表す
これらの名前修飾により、コンパイラは異なる関数やテンプレートのインスタンスを一意に識別できます。名前修飾は、同じ名前の関数が異なるスコープや異なる型を持つ場合に、それらを区別するために使用されます。
C++からのリンク時のCシンボルの処理
[編集]C++プログラムからCシンボルにリンクする場合、C++コンパイラはCシンボルを名前修飾します。これは、CリンカがC++シンボルを正しく認識できるようにするためです。
Cシンボル名を保持する必要がある場合は、extern "C"
キーワードを使用できます。このキーワードは、CリンカにシンボルをCシンボルとして認識させるように指示します。
C++での標準名前修飾
[編集]C++標準では、名前修飾を標準化していません。これは、異なるコンパイラが異なる名前修飾スキームを使用している可能性があることを意味します。
C++ ABI(Application Binary Interface)が標準化されているプラットフォームでは、名前修飾も標準化されています。これにより、異なるコンパイラでコンパイルされたコードをリンクすることができます。
C++名前修飾の実世界への影響
[編集]名前修飾は、C++開発において重要な役割を果たします。これは、ソフトウェアの相互運用性と互換性を保証するのに役立ちます。
名前修飾がなければ、異なるコンパイラでコンパイルされたコードをリンクすることはできません。これは、C++プログラムの開発と配布を困難にする可能性があります。
デマングリング
[編集]デマングリングとは、名前修飾されたシンボルを人間が読みやすい形式に変換するプロセスです。これは、デバッガやその他のツールで名前修飾されたシンボルを理解するために役立ちます。
デマングリングには、さまざまな方法があります。最も一般的な方法は、c++filt
ユーティリティを使用する方法です。c++filt
は、多くのC++コンパイラに付属しています。
$ c++filt -n _Z3addii
add(int, int)
まとめ
[編集]C++における名前修飾は、複雑なトピックですが、理解することは重要です。これは、C++の多重オーバーロードやテンプレートなどの機能をサポートするために必要不可欠なものです。また、ソフトウェアの相互運用性と互換性を保証するのにも役立ちます。
この章では、名前修飾の概要、歴史的背景、単純な例、複雑な例、C++からのリンク時のCシンボルの処理、C++での標準名前修飾、C++名前修飾の実世界への影響、デマングリングなどを説明しました。
今後の展望
[編集]C++の名前修飾は、今後も進化し続ける可能性があります。将来のバージョンでは、名前修飾スキームが標準化される可能性があります。また、デマングリングの精度が向上する可能性もあります。