コンテンツにスキップ

オブジェクトファイル・実行ファイル・静的ライブラリ・動的ライブラリ・DLL

一言で言うと

  • オブジェクトファイル(.o / .obj): 1翻訳単位ぶんの機械語+シンボル表。未完成品
  • 静的ライブラリ(.a / .lib): .oを束ねた「アーカイブ」。リンク時に実行ファイルへ焼き込まれる
  • 動的ライブラリ(.so / .dll): 完成品に近いバイナリ。実行時にロードして結合される
  • 実行ファイル(.exe / ELF): ローダーが読める完成品

オブジェクトファイルの中身

enemy.o
├─ .text   : 機械語(Enemy::TakeDamage の命令列など)
├─ .data   : 初期化済みグローバル/静的変数の初期値
├─ .bss    : ゼロ初期化変数の「サイズだけ」(実体は実行時に確保)
├─ .rodata : 定数・文字列リテラル
├─ シンボル表: 提供する名前 / 必要とする名前(未解決)
└─ 再配置情報: 「この命令のこの部分は、後でXのアドレスを書き込む」のリスト

「未解決の名前」と「再配置情報」があるから未完成品であり、それを埋めるのがリンカです。

静的ライブラリ vs 動的ライブラリ

静的リンク:                          動的リンク:
game.exe                            game.exe ──実行時に参照──> physics.dll
 ├─ main.o                           ├─ main.o                  (別ファイルのまま)
 ├─ physics.a の中の使われた.o        └─ 「physics.dllのSimulateを使う」という記録
 └─ ...(全部入り・自己完結)
静的リンク 動的リンク(DLL/.so)
実行ファイル 大きい(全部入り) 小さい(参照だけ)
起動・実行 速い・自己完結 ロードの手間、ファイルが欠けると起動不能
更新 再ビルド・再配布が必要 DLL差し替えだけで更新可(パッチ、プラグイン)
複数プログラムでの共有 各自にコピー メモリ・ディスクを共有できる
最適化 LTO(リンク時最適化)でライブラリ境界を越えたインライン化が可能 境界を越えた最適化は不可
ABI問題 ビルド時に固定(比較的安全) 実行時に一致が必要(→ 下記)
  • 静的ライブラリのリンクは「使われた.oだけ取り込む」(アーカイブ全体ではない)。「リンクしたのにシンボルが見つからない」の一因はリンク順・参照順(→ リンクエラー)
  • Windowsでは動的リンクでもインポートライブラリ(.lib)をリンク時に使う(「このDLLにこの関数がある」という目録)。「.libには静的ライブラリとインポートライブラリの2種類がある」ことは混乱の名所

DLLの実務(Windows/ゲーム)

// DLL側: 公開する関数に印を付ける(Windows)
extern "C" __declspec(dllexport) int PluginVersion() { return 3; }
// 使う側(暗黙リンク): ヘッダー+インポートライブラリで普通の関数のように呼ぶ
// 使う側(明示ロード): LoadLibrary("plugin.dll") → GetProcAddress("PluginVersion")
  • C++の型・クラスをDLL境界で渡すのはABIの罠(→ 前ページ)。異なるコンパイラ/設定/CRTでビルドされたDLLとexeの間でstd::stringやnew/deleteの所有権を渡すと壊れる。境界はC ABI+プレーンなデータが鉄則
  • 「確保したモジュールが解放する」(DLL内でnewしたものはDLL内でdelete)——CRTが別だとヒープも別のため

ゲーム開発での使われ方

  • エンジン・ミドルウェアの配布形態: FMOD/Steamworks等はDLL+Cヘッダ(まさにABI安全策)
  • ホットリロード: ゲームロジックをDLL化し、実行中に差し替えて反映(開発イテレーション高速化)。UEのLive Codingもモジュール(DLL)差し替えが基盤(→ 第10部)
  • プラグイン/MOD: 明示ロード(LoadLibrary)+extern "C"エントリポイント
  • コンソール機では静的リンクが基本(単一実行ファイル文化)——プラットフォームで流儀が変わる

C#との対応

  • C#の.dll(アセンブリ)は別物: 機械語ではなくIL+メタデータで、ロード・結合は.NETランタイムが行い、ABI問題がない。「UnityのManaged DLL」と「ネイティブDLL」は同じ拡張子の別世界
  • Unityの Plugins/ フォルダのネイティブプラグイン(.dll/.so/.dylib)がこのページの動的ライブラリで、P/Invoke([DllImport])がGetProcAddressに相当
  • Assembly Definition(.asmdef)は「C#コードの静的ライブラリ分割」に似た開発時の構造化(→ 第9部)

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

  1. 「DLLを置けば動く」— ネイティブDLLはプラットフォーム・アーキテクチャ(x64/ARM64)・依存DLL(VCランタイム等)の一致が必要
  2. C# DLLの感覚でネイティブDLLにクラスを公開しようとする——境界はCの関数とデータに落とすのが定石
  3. 「実行ファイルは1つの塊」— 実際は起動時に多数のDLL(システム・ドライバ層含む)と結合されて動いている(Process Explorer等で見られる)

使う場面 / 使わない場面

  • 静的リンク: 自プロジェクトの内部分割(既定)、コンソール機、配布を単純にしたいツール
  • 動的リンク: ミドルウェアSDK、プラグイン/MOD、ホットリロード、更新パッチをexe再配布なしで当てたい場合
  • 内部コードをむやみにDLL分割しない——境界維持(ABI・エクスポート管理)のコストは安くない

理解度チェック

  1. .oが「未完成品」である理由を2つの中身(未解決シンボル・再配置情報)で説明できますか。
  2. 静的/動的リンクのトレードオフを3つ挙げてください。
  3. DLL境界でstd::stringを渡すことが危険な理由は?

演習

samples/tu_demo/ar rcs libenemy.a enemy.o として静的ライブラリを作り、g++ main.o radar.o -L. -lenemy でリンクしてください。次にリンク順を変えて(-lenemyを先頭に)エラーが出るか観察してください(GNU ldのリンク順依存の体験)。


前: シンボルとリンケージ | カテゴリ目次 | 次: テンプレートの実体化とinlineの実際