コンテンツにスキップ

シンボル・名前修飾・リンケージ・ABI

一言で言うと

  • シンボル: リンカが使う「名前→機械語/データの場所」の項目。関数・グローバル変数が持つ
  • 名前修飾 (name mangling): C++の関数名を、型情報込みの一意な文字列に変換する仕組み(オーバーロードを区別するため)
  • リンケージ: その名前が翻訳単位を越えて見えるか(外部リンケージ)、内に閉じるか(内部リンケージ)
  • ABI (Application Binary Interface): バイナリレベルの互換性の取り決め(修飾規則、呼出規約、レイアウト)。コンパイラが違うとC++バイナリは混ぜられないことの根拠

シンボルと名前修飾

// これをコンパイルすると…
int Add(int a, int b);
float Add(float a, float b);
namespace game { void Boot(); }
シンボル表(GCC/Clangの Itanium ABI 方式の例):
  _Z3Addii      ← Add(int, int)     「Z3Add」+引数型「ii」
  _Z3Addff      ← Add(float, float)
  _ZN4game4BootEv ← game::Boot()
(MSVCは別方式: ?Add@@YAHHH@Z のような形。同じ言語でも修飾は処理系ごと)
  • オーバーロードが可能な理由そのもの: 型情報を名前に埋め込むから、リンカには別々の名前に見える
  • リンクエラーの undefined reference to _Z3Addii はこの修飾名。c++filt(GCC系)で人間可読に戻せる
  • extern "C": 修飾を止めてC言語形式の素の名前にする。C・他言語・DLL境界との接続に使う(そのためオーバーロード不可)
extern "C" int PluginEntry(int version);   // シンボル名は素の "PluginEntry"

リンケージ(名前の可視範囲)

リンケージ 意味 書き方
外部リンケージ 他の翻訳単位から参照できる(既定: 通常の関数・グローバル変数) 何もしない
内部リンケージ この翻訳単位内のみ 無名namespace(推奨)/ static(旧式)
リンケージなし ローカル変数など
// a.cpp
namespace {                    // 内部リンケージ: 他の.cppと名前が衝突しない
    int helper = 0;
    void LocalUtil() {}
}
int g_score = 0;               // 外部リンケージ: プログラムに1つだけ(他の.cppからextern参照可)

ヘッダーに「外部リンケージの変数の定義」を書くと多重定義になる(ODR)。inline 変数(C++17)がその解決策です。

用語注意: static/dynamic linkage という言い方

「static linkage」は文脈で2つの意味に使われます: (1) 上記の内部リンケージ(staticキーワード由来)、(2) 静的リンク(ライブラリを実行ファイルに焼き込むこと)。同様に「dynamic linkage」は動的リンク(DLL/共有ライブラリを実行時に結合)を指すのが普通です。本Wikiでは (2) の意味は「静的リンク/動的リンク」と表記し、ライブラリのページで扱います。

ABI — バイナリ互換の約束

ABIは「コンパイル済みバイナリ同士が正しく会話するための全ルール」:

  • 名前修飾の方式
  • 呼出規約(引数をどのレジスタ/スタックで渡すか)
  • 構造体のレイアウト・アラインメント
  • vtableの配置、例外・RTTIの実装方式
  • 標準ライブラリの内部構造(std::stringのメモリ上の形)

実務での帰結

  1. MSVCでビルドした.libとGCC(MinGW)でビルドした.oは混ぜられない(修飾も規約も違う)。Windowsのゲーム開発でMinGW製ライブラリとVS製プロジェクトが繋がらない理由
  2. 同じコンパイラでもバージョン・設定違いで壊れることがある(MSVCのDebug/Releaseのイテレータサイズ違い、_ITERATOR_DEBUG_LEVEL不一致 → リンクエラーや実行時破壊)
  3. C++の型をDLL境界で渡すのは危険(std::stringを渡す=内部レイアウトの一致を要求)。安全なのはCのABI(extern "C"+プレーンな型)——ミドルウェアのSDKがCインターフェースな理由(→ DLLABI互換性)
  4. Itanium ABI(GCC/Clang系が採用)とMSVC ABIの2大陣営、と大づかみしておけば実務は足りる

C#との対応

  • C#のメタデータ+ILは「自己記述的」で、アセンブリ間の互換は.NETランタイムが保証する——ABI問題が原則存在しない(だからNuGetで気軽にバイナリを混ぜられる)
  • C++/C#相互運用(P/Invoke)は、まさに extern "C" のC ABIを経由して行う(→ 第9部)
  • UnityのネイティブプラグインでC++関数を呼ぶときの [DllImport] + extern "C" がこのページの内容の実践

Unity開発者が誤解しやすい点

  1. 「DLLは言語問わず繋がる」— C ABIならほぼ真、C++ ABIは同一コンパイラ・同一設定でのみ
  2. リンクエラーの ?TakeDamage@Enemy@@QEAAXH@Z を見て絶望しない——修飾名は機械的に読める(undname/c++filtで復元可能)。「どの関数のシンボルが無いのか」は必ず特定できる
  3. 「externは変数のキーワード」— extern "C" は別物(リンケージ指定)

ゲーム開発での使用例

  • プラグインシステム(DLLからextern "C"のエントリポイントを取得)
  • ミドルウェア統合時のリンクエラー解読(修飾名→どのライブラリの何が足りないか)
  • 内部リンケージ(無名namespace)でヘルパーを隠し、シンボル衝突とリンク時間を減らす

理解度チェック

  1. 名前修飾はなぜ必要ですか(何を可能にしているか)。
  2. 内部リンケージにする手段と、その効果は?
  3. 「MSVC製の.libをMinGWで使えない」理由をABIの語彙で説明してください。

演習

samples/tu_demo/ をビルド後、nm enemy.o(MinGW)でシンボル表を眺め、(a) Enemy::TakeDamageの修飾名を見つけて c++filt で復元、(b) 無名namespace内の関数がどう見えるか(ローカルシンボルt表示)を確認してください。


前: 翻訳単位とヘッダー | カテゴリ目次 | 次: オブジェクトファイルとライブラリ