シンボル・名前修飾・リンケージ・ABI¶
一言で言うと¶
- シンボル: リンカが使う「名前→機械語/データの場所」の項目。関数・グローバル変数が持つ
- 名前修飾 (name mangling): C++の関数名を、型情報込みの一意な文字列に変換する仕組み(オーバーロードを区別するため)
- リンケージ: その名前が翻訳単位を越えて見えるか(外部リンケージ)、内に閉じるか(内部リンケージ)
- ABI (Application Binary Interface): バイナリレベルの互換性の取り決め(修飾規則、呼出規約、レイアウト)。コンパイラが違うとC++バイナリは混ぜられないことの根拠
シンボルと名前修飾¶
シンボル表(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境界との接続に使う(そのためオーバーロード不可)
リンケージ(名前の可視範囲)¶
| リンケージ | 意味 | 書き方 |
|---|---|---|
| 外部リンケージ | 他の翻訳単位から参照できる(既定: 通常の関数・グローバル変数) | 何もしない |
| 内部リンケージ | この翻訳単位内のみ | 無名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のメモリ上の形)
実務での帰結¶
- MSVCでビルドした.libとGCC(MinGW)でビルドした.oは混ぜられない(修飾も規約も違う)。Windowsのゲーム開発でMinGW製ライブラリとVS製プロジェクトが繋がらない理由
- 同じコンパイラでもバージョン・設定違いで壊れることがある(MSVCのDebug/Releaseのイテレータサイズ違い、_ITERATOR_DEBUG_LEVEL不一致 → リンクエラーや実行時破壊)
- C++の型をDLL境界で渡すのは危険(std::stringを渡す=内部レイアウトの一致を要求)。安全なのはCのABI(extern "C"+プレーンな型)——ミドルウェアのSDKがCインターフェースな理由(→ DLL、ABI互換性)
- 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開発者が誤解しやすい点¶
- 「DLLは言語問わず繋がる」— C ABIならほぼ真、C++ ABIは同一コンパイラ・同一設定でのみ
- リンクエラーの
?TakeDamage@Enemy@@QEAAXH@Zを見て絶望しない——修飾名は機械的に読める(undname/c++filtで復元可能)。「どの関数のシンボルが無いのか」は必ず特定できる - 「externは変数のキーワード」—
extern "C"は別物(リンケージ指定)
ゲーム開発での使用例¶
- プラグインシステム(DLLからextern "C"のエントリポイントを取得)
- ミドルウェア統合時のリンクエラー解読(修飾名→どのライブラリの何が足りないか)
- 内部リンケージ(無名namespace)でヘルパーを隠し、シンボル衝突とリンク時間を減らす
理解度チェック¶
- 名前修飾はなぜ必要ですか(何を可能にしているか)。
- 内部リンケージにする手段と、その効果は?
- 「MSVC製の.libをMinGWで使えない」理由をABIの語彙で説明してください。
演習¶
samples/tu_demo/ をビルド後、nm enemy.o(MinGW)でシンボル表を眺め、(a) Enemy::TakeDamageの修飾名を見つけて c++filt で復元、(b) 無名namespace内の関数がどう見えるか(ローカルシンボルt表示)を確認してください。
前: 翻訳単位とヘッダー | カテゴリ目次 | 次: オブジェクトファイルとライブラリ