マングリング
シンボル名とはリンカが管理する必要のある実体に与えられた名前です。
具体的には
- 関数本体(フリー関数、メンバ関数、コンストラクタ、デストラクタ)
- 仮想関数テーブル
- 静的データメンバおよび静的変数
これらにシンボル名が与えられます。
このとき、コンパイラは一定のルールに従ってメンバ要素の場合はクラス名、関数の場合は返り値型、呼び出し形式、引数型などを埋め込んだシンボル名を作ります。
この付加情報をつけた名前をつくることをマングルまたはマングリングといいます。日本語だとシンボル名修飾とか名前修飾といういい方もしますね。
たとえば、MSVC の場合は
template <typename T>
struct CLASSNAME {
int foo( T value );
};
このコードの
public: int __cdecl CLASSNAME::foo(int)
というメンバ関数は
?foo@?$CLASSNAME@H@@QEAAHH@Z
というシンボル名になります。
英語での mangle というのは「ぐちゃぐちゃに壊す」という意味だそうです。
リンクエラーがでたときの、ついバッファオーバーランの心配をしてしまう、あのやたらと長いシンボル名をみると、その意味も納得の混沌さです。
このシンボル名の上限は MSVC の場合で約4,000文字です。
Visual Studio 6.0 や Visual Studio.NET(2002/2003)の頃の上限は、255文字でした。なのでSTLを使うと、ほぼ必ずシンボル名を切り詰めたという警告が出てしまっていました。シンボル名を切り詰められてもとくに大きな不具合を感じなかったんですが、あれは大丈夫だったんでしょうか?
それが約4,000文字になって安泰と思いきや、最近の新しい C++ では、4,000でも足りなくなるという20年前の悪夢が再来しようとしています。
その原因は std::ranges::views のパイプライン接続とラムダ式でs(……warning C1234: 話が長いので切り詰めます)
……要するにテンプレートパラメータを与えられインスタンス化されたクラステンプレートにはテンプレートパラメータつきのクラス名が作られ、そのクラス名がマングられた関数や変数の長いシンボル名をキーにしてリンカは重複コードを破棄するというわけです。
Cにはマングルという概念はありません。そもそも関数のオーバーロードがないので関数名だけでよかったのです。
昔の C は
int foo(int)
という関数があるときのシンボル名は _foo となっていました。前に _ をつけるだけというシンプルさです。
リンクエラーがでたときの
_foo が未解決シンボルです。
という静かで控えめなエラーメッセージをみると、私は故郷の田園風景を見たような気分になります。PC-9801VX で動く telnet。そこに表示された vi の画面。そして csh のプロンプト。SunOS 4.2か……何もかも皆懐かしい。
ちなみに C++ の世界でこの _foo というシンプルなシンボル名を作り出すのが extern "C" です。
二段階名前探索
原付で右折専用レーンを含む片側3車線以上の交差点を右折するときは、二段階右折をしなければならないように、コンパイラは{関数/クラス}テンプレートをインスタンス化したオブジェクトコードを作り出すのに二段階名前探索(Two-phase name lookup)を行います。
なぜ二段階なのでしょうか?
それは
- テンプレートパラメータの型や値がまだ確定していないとき
- テンプレートパラメータの型や値が確定してインスタンス化するとき
という2つのフェーズで処理を行うからです。
テンプレートのコードを書くとき、従来までは X::type と書けていたのに、ある時期から typename X::type と書かないといけなくなった経験はありませんか?
これも MSVC の洗礼の1つです。そのときのエラーメッセージは「依存名」がどうのこうのって言ってたでしょう?
この「依存名」と「非依存名」こそが、本稿のメインテーマとなるものです。
「四角いジャングル」に黒崎健時師が登場したときのような仰々しい紹介の仕方になりました。すみません、これが出てくるまで、めっちゃ引っ張りましたねー。
依存名とは、何に依存しているのでしょうか?
それはテンプレートパラメータに対してです。
- 非依存名 : テンプレートパラメータが何であるかに影響されない識別子。
- 依存名 : テンプレートパラメータに依存して決まる識別子。
コンパイラはテンプレートパラメータが X であるとき X::type と書かれているとデフォルトで値( X 内の列挙子や静的メンバ)であると解釈します。
しかし、型である可能性もあって、その場合は typename X::type と型であることを明示してやる必要があるのです。
構文解析時に現れた識別子が値なのか、型なのかで適用する構文ルールはまるっきり変わるのでそれだけは先に判別しておく必要があるのでしょう。
古い MSVC ではこの工程に厳密さを欠いていて X::type と書かれていたときの解釈をインスタンス生成時にまで後回しにしていました。
どうせ X が確定すると、type の正体はめくれるんだから型か値か、曖昧でもオレはコンパイルできるんだぜ。オレはやるぜ。オレはやったぜ。
と MSVC さんにシベリアンハスキーの姿が重なります。
(つづく)