はじめに

編集

名前修飾( 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.ollvm-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: 関数名 add3 は関数名の長さ
  • ii: 関数の引数の型。iint 型を表す。ここでは2つの int 型の引数を表すため、ii となる
関数 auto add(double x, double y) -> double の名前修飾 _Z3adddd
  • _Z: C++の名前修飾接頭辞(Itanium C++ ABIを使用)
  • 3add: 関数名 add3 は関数名の長さ
  • dd: 関数の引数の型。ddouble 型を表す。ここでは2つの double 型の引数を表すため、dd となる

これらの名前修飾により、同じ名前の関数であっても、異なる引数の型に基づいて一意に識別できるようになります。名前修飾は、コンパイラが異なる関数オーバーロードやテンプレートのインスタンスを正しく区別するために使用されます。

nmllvm-nm、および gcc-nm
nmllvm-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: 関数名 add3 は関数名の長さ
  • ii: 関数の引数の型。iint 型を表す。ここでは2つの int 型の引数を表すため、ii となる
関数 add(double, double) の名前修飾 _Z3adddd
  • _Z: C++の名前修飾接頭辞(Itanium C++ ABIを使用)
  • 3add: 関数名 add3 は関数名の長さ
  • dd: 関数の引数の型。ddouble 型を表す。ここでは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.ollvm-nm を使用して調べます。

llvm-nm の出力結果には、それぞれの関数の名前修飾されたシンボルが表示されます。add 関数と sadd 関数の名前修飾されたシンボルがそれぞれ _Z3addIsET_S0_S0__Z4saddss です。

テンプレート関数 add<short>(short, short) の名前修飾 _Z3addIsET_S0_S0_
  • _Z: C++の名前修飾接頭辞(Itanium C++ ABIを使用)
  • 3add: 関数名 add3 は関数名の長さ
  • IsE: テンプレート引数 shortI はテンプレート引数の始まりを示し、sshort 型、E はテンプレート引数の終わりを示す
  • T_S0_S0_: 関数の引数の型。T_ はテンプレート引数に基づく戻り値の型、S0_ はテンプレートの第1引数 (short) を表し、もう一つの S0_ は第2引数 (short) を表す
通常の関数 sadd(short, short) の名前修飾 _Z4saddss
  • _Z: C++の名前修飾接頭辞(Itanium C++ ABIを使用)
  • 4sadd: 関数名 sadd4 は関数名の長さ
  • ss: 関数の引数の型。sshort 型を表す

これらの名前修飾により、コンパイラは異なる関数やテンプレートのインスタンスを一意に識別できます。名前修飾は、同じ名前の関数が異なるスコープや異なる型を持つ場合に、それらを区別するために使用されます。

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++の名前修飾は、今後も進化し続ける可能性があります。将来のバージョンでは、名前修飾スキームが標準化される可能性があります。また、デマングリングの精度が向上する可能性もあります。