TL;DR

コンパイラが関数を一意に識別するために、関数名に手を加えること。関数のシグネチャや型情報を名前にエンコードして、オブジェクトファイルに書き出す。

なぜ必要か

一般に、コンパイル型の言語は分割コンパイルをサポートしている。分割コンパイルでは、リンカがオブジェクトファイル同士を結び付ける(リンクする)。リンカは、名前が一致するかどうかだけで、どのオブジェクトファイルを結び付けるかを判断する。

そのため C では、関数や変数をグローバルな名前空間で管理し、名前が一意であることを求める。この結果、C のライブラリでは、lib1_process と lib2_process のように、すべての関数に接頭辞を付けるのが慣習になった。

しかし C++ では、オーバーロードや名前空間などの機能が入ったことで、この方法では対応できなくなった。次のコードでは、同じ add という名前の関数が 3 つある。

namespace ns1 {
  int32_t add(int32_t a, int32_t b) { return a+b; }
  int64_t add(int64_t a, int64_t b) { return a+b; }
}
 
namespace ns2 {
  int32_t add(int32_t a, int32_t b) { return a+b; }
}

仕組み

マングリングでは、オーバーロードされた関数のシグネチャや型情報を名前にエンコードし、オブジェクトファイルに書き出す。これにより、名前だけでオブジェクトファイルを結び付けるリンカの処理を変えずに、同じ名前の関数を区別できるようになった。

例

オブジェクトファイルに nm(name list)コマンドを使うと、リンカが扱う名前を確認できる。

# gcc(C)で生成したオブジェクトファイルの場合
$ nm lib.o | grep add
0000000000000000 T _add
 
# g++(C++)で生成したオブジェクトファイルの場合
$ nm cpp-lib.o | grep add
0000000000000000 T __Z3addii

C++ の出力では、名前がマングリングされていることが分かる。T は、関数がテキストセクション(テキスト領域)にあることを示す。

c++filt コマンドを使うと、マングリングされた名前を読める C++ の形に戻せる。

$ nm cpp-lib.o | grep add | c++filt
0000000000000000 T add(int, int)

関連

出典