目次

ビルドの仕組みや静的ライブラリ, 動的ライブラリの違いなどをメモ

[参考記事]

ビルドとは

材料(複数のソースファイルなど)から成果物(ライブラリや実行ファイル)を生成する処理

Image

ビルドの仕組み

  1. プリプロセッサ
  2. コンパイラ
  3. アセンブラ
  4. リンカ

Image

1. プリプロセッサ

コンパイルのための下準備をする

  • ソースファイル(*.c, *.cppなど)1つ1つに対して以下のような処理をする
    • #include`/#define`などのプリプロセッサディレクティブの解決
    • コメントの削除
  • 特に重要なのが#includeの解決
    • ソースファイルの中で#includeが指定されていた場合、プリプロセッサは指定されたヘッダファイルを探してきて、#includeの位置にヘッダファイルの中身をそのまま貼り付ける
    • 同じヘッダファイルを何度もインクルードするのは効率が悪い
    • → ヘッダファイルを書くときは#pragma onceなどを使って1度しかインクルードしないようにする

2. コンパイラ

  • コンパイルする
    • C/C++で書かれたファイル(人間が読み書きしやすいように設計された高水準言語)をアセンブラ言語(コンピュータが理解しやすい形式、機械語を扱いやすいように直した低水準言語)のファイルに変換(翻訳)する
  • プリプロセッサから受け取ったソースファイルを1つ1つアセンブラ言語のファイルに変換する
  • 他のファイルで定義されている関数の実装は保留した状態(外部参照が未解決である状態)のアセンブラ言語のファイルがソースファイルの数だけ作成される
    • ヘッダファイル, プロトタイプ宣言で実装の中身は保留

3. アセンブラ

  • アセンブルする
    • コンパイラで生成されたアセンブラ言語のファイルを機械語のファイル(オブジェクトファイルとかバイナリファイルと呼ぶ)に変換する
  • CPUのアーキテクチャに合わせた機械語が生成される

4. リンカ

  • リンクする
    • 作成された複数のオブジェクトファイルを1つにまとめる
  • main関数のあるオブジェクトファイルから芋づる式に保留された関数(未解決の外部参照)の実装を探索していく
  • 必要な情報をすべて集めて1つの実行ファイル/ライブラリファイルを作成する

ライブラリの種類と仕組み

  • ライブラリ
    • ビルドの管理を楽にしたり他人の描いたプログラムを再利用したりするためにプログラムをいくつかの単位に分割する仕組み
    • stdio.hstdlib.hなどの標準ライブラリもこの仕組を利用している

ライブラリの方式は大きく分けて4つある

  1. ソースコードを配布
  2. 静的リンク(static linking): 静的リンクライブラリ (static link library)
  3. 動的リンク(dynamic linking): 動的リンクライブラリ (dynamic link library)
  4. 動的読み込み(dynamic loading): 動的リンクライブラリ (dynamic link library)

1. ソースコードを配布

  • (他人が書いた)ソースコードをそのまま配布
  • ライブラリのコードを1つのヘッダファイルにまとめたシングルファイルライブラリとかよくある (picojsonとか?)

Image

利用方法

自分が書いたソースコードと全く同じようにビルドする

利点

  • 利用環境に合わせてビルドできる
  • ソースコードを利用者側で改変できる

欠点

  • 利用者側でビルドする手間がかかる

2. 静的リンク

  • ライブラリのソースコードを事前にコンパイルしオブジェクトコードの状態で配布
  • 通常は複数のオブジェクトファイルをまとめた静的リンクライブラリファイル(つまりオブジェクトファイルをまとめたただのアーカイブ)の形で配布される
    • OSX/Linux: lib*.a
    • Windows: *.lib

Image

利用方法

静的ライブラリを自分のプログラムに組み込むためには、ビルド時にヘッダファイル静的リンクライブラリファイルが必要

  • 自分のソースコードにこのヘッダファイルを#include
  • リンクのタイミングで静的リンクライブラリを混ぜ込むようにコンパイルオプションを設定してビルド
  • → ライブラリのコードも内包した実行ファイルが作成される

ex) gccでlibmylib.aを混ぜながらmain.cをビルドする
gcc -o main main.c -I <mylib.hがあるディレクトリへのパス> -L <libmylib.aがあるディレクトリへのパス> -l mylib

  • -I: ライブラリのヘッダファイルの場所
  • -L: ライブラリファイルの場所
  • -l: ライブラリの名前

利点

  • 全てのコードが実行ファイルにまとまるため取り回しが楽
  • 最初にまとめてメモリにロードされるため実行が高速
  • コンパイル済みの状態で配布するためライブラリ利用者の手間が減る

欠点

  • 同じライブラリを利用するプログラムが複数あっても、それぞれの実行ファイルにライブラリのコードが丸コピされる
    • ファイルサイズが大きくなりがち
  • 起動時のメモリへのロード時間が長くなる
  • フローによって使われないコードもメモリにロードされるためメモリ使用量が増える

3. 動的リンク

  • リンクが不完全な実行ファイルを作成しておいて実行時に動的リンクライブラリ(共有ライブラリ)とリンクを行う
  • コンパイラのオプションを適当に設定してビルドすると動的リンクライブラリファイルが作成される
    • OSX: lib*.dylib
    • Linux: lib*.so
    • Windows: *.dll

Image

利用方法

動的リンクライブラリを自分のプログラムから利用するときは、ヘッダファイル動的リンクライブラリファイルが必要

  • 自分のソースコードにこのヘッダファイルを#include
  • 動的リンクライブラリの情報を混ぜ込むようにコンパイルオプションを設定してビルド
  • → 一部のコードを内包しない代わりに動的リンクの情報を持った実行ファイルが作成される

  • この実行ファイルが起動され、動的リンクになっている箇所を実行することになった段階で、動的リンクライブラリファイルの探索とメモリへのロードが行われ、ライブラリ側のコードが実行される

  • 実行時に動的リンクライブラリを発見できるようにライブラリファイルへのパスを指定しておく必要がある

  • 一般的なライブラリでは動的リンクライブラリを単体で配布せず、動的リンクの情報を持った静的ライブラリファイル(*.a or *.lib)と動的ライブラリファイル(*.so or *.dylib)の組み合わせを配布していることが多い
  • → ビルドの際には一律に静的ライブラリだけ読み込めばいい
  • (もちろん、この静的ライブラリは処理の実装を持っていないため、実行時に動的リンクが行われる)

利点

  • よく使うライブラリを動的ライブラリにしておくことでファイル容量・メモリ使用量を削減できる
  • ライブラリの内部実装だけを変えるとき、実行ファイルをビルドし直す必要がない
  • 必要になった時点でメモリにロードされるため、フローによっては呼び出されないライブラリがあるときにメモリの節約になる
  • コンパイル済みの状態で配布するためライブラリ利用者の手間が減る

欠点

  • 実行時、初めて動的ライブラリの関数を参照するときにメモリのロード時間が長くなる
  • 実行環境に動的ライブラリが正しく配置されているか配慮する必要がある
    • ライブラリに互換性のないバージョン変更があったとき、実行ファイルの更新を忘れてしまっても実行してみるまでミスに気づかない
    • Pathの通し忘れなどで動的ライブラリが発見できなかった場合、プログラムがクラッシュする

4. 動的読み込み

  • 動的リンクを拡張したような利用形式
  • ビルドの段階ではライブラリ関連のファイルを一切必要としない
  • その代わり自分のソースコードの中で明示的に「**という名前の動的リンクライブラリの中のxxという関数を呼び出す」という風に記述しておく必要がある
  • ライブラリファイルを後付けで動的リンクすることができる

Image

利用方法

多くの環境ではdlfcn.hを通して動的読み込みの機能が提供されている
ライブラリ側は動的リンクライブラリファイルをそのまま使用できる

利点

  • ライブラリファイルの探索やロードの処理もプログラム上で細かく実装できる
  • 実行ファイルのコンパイルの時点でライブラリ側が完成していなくてもよい
    これらの利点より、プラグイン開発に利用される利用されることが多い

欠点

  • 実行ファイルの作成の時点ではライブラリの名前と関数名が文字列として埋まっているだけ
  • コンパイル時の型チェックの恩恵を受けられない
  • → 型や関数名の管理を手動でしなければならない