🐧 Linux 総合学習プラットフォーム
C・ビルドツール ・ 上級

ライブラリを作る(ar・-fPIC・soname)

自分のコードを他のプログラムから使い回したくなったら、ライブラリという形にまとめます。作り方には静的ライブラリ(.a)と共有ライブラリ(.so)の2通りがあり、コマンドも性質もまったく異なります。ここではarで.aを束ねる方法、-fPICと-sharedで.soを作る方法、そしてsonameによるバージョン管理までを一通り扱います。以前ldd実践で見た「.soへの依存」が、今度は自分の作る側の視点で見えてくる回です。

自分の書いた関数を、別のプログラムからも使い回したくなることがある。そのたびにソースをコピーするのではなく、ひとまとめにした部品として配れる形にするのがライブラリだ。

🔗
たとえライブラリは「部品箱」だ。自分で作ったネジや金具を、毎回一から削り出す代わりに、箱にまとめておいて必要なときに取り出す。C言語での部品箱の作り方には、大きく2つの流派がある。

📦 静的ライブラリ——arで.oを1つに束ねる

ひとつめは静的ライブラリ(cba-static-lib)で、拡張子は.aになる。作り方はいたってシンプルで、複数の.oファイルをarコマンドで1つの.aファイルに束ねるだけだ。

$ gcc -c mathutil.c -o mathutil.o $ ar rcs libmathutil.a mathutil.o r は「追加・更新」、c は「アーカイブを新規作成」、s は「索引を作る」という意味のオプションで、この組み合わせがライブラリ作成の定番の書き方になっている。
💡
ポイントarはtarに似た「複数ファイルを1つに束ねる」道具だが、中身はコンパイル済みオブジェクトを想定している。束ねた結果が静的ライブラリ.aになる。

静的ライブラリをリンクすると、必要な部分がそのまま実行ファイルの中にコピーされる。だから配布するのは実行ファイル1つで済み、相手の環境にライブラリを別途インストールしてもらう必要がない。そのぶん実行ファイルのサイズは大きくなりやすく、複数のプログラムが同じ静的ライブラリを使っていても、それぞれの実行ファイルの中に同じコードが重複して埋め込まれる。

🔧 共有ライブラリ——-fPICと-sharedで.soを作る

もうひとつが共有ライブラリ(.so)で、以前ldd実践で「実行ファイルが依存している」と確認したのがまさにこれだ。今回は、その.soを自分で作る側に回る。

共有ライブラリを作るには、コンパイル時に-fPIC(cba-fpic)を付け、リンク時に-sharedを付ける。-fPICは「位置に依存しないコード」を生成し、どのメモリアドレスに読み込まれても動くようにするオプションだ。

$ gcc -fPIC -c mathutil.c -o mathutil.o $ gcc -shared -o libmathutil.so mathutil.o 静的ライブラリのarとは違い、共有ライブラリはgcc自身に-sharedを渡して作る。ここが混同しやすいポイントだ。
mathutil.cgcc -c → ar rcslibmathutil.a-fPIC → -sharedlibmathutil.so実行ファイルにコードごと同梱実行時に読み込み複数プロセスで共有
コツ-fPICを付け忘れて共有ライブラリを作ろうとすると、環境によってはリンクエラーになったり、実行時に不可解な不具合が出ることがある。.soを作る前提のオブジェクトには必ず-fPICを付ける、と決めておくとよい。

🏷️ sonameとバージョン番号

共有ライブラリは複数のプログラムから同時に使われる前提のため、後からアップデートしても互換性を壊さないための仕組みが用意されている。それがsoname(cba-soname)だ。

実務でよく見る形は、実体ファイル名がlibmathutil.so.1.0.0で、そこにlibmathutil.so.1というsonameのシンボリックリンクが張られ、さらに開発用にlibmathutil.soというリンクが重ねられる、という3段構成だ。

開発用リンクlibmathutil.sosonamelibmathutil.so.1libmathutil.so.1.0.0(実体)コンパイル時に指定実行時に検索される名前パッチ版まで含む実ファイル
💡
ポイントsonameのおかげで、パッチ版(1.0.0→1.0.1)を入れ替えてもプログラム側は同じlibmathutil.so.1を探しにいくだけで動き続けられる。互換性が壊れる大きな変更のときだけ、soname側の数字(1→2)を上げる。

🔗 -L・-l・LD_LIBRARY_PATHでつなぐ

作ったライブラリを使う側では、-L でライブラリを探すディレクトリを、-l でライブラリ名(先頭のlibと拡張子を除いた部分)を指定してリンクする。

$ gcc main.c -L. -lmathutil -o app -L. はカレントディレクトリを検索対象に加え、-lmathutilはlibmathutil.aまたはlibmathutil.soを探せ、という指示になる。

共有ライブラリの場合、実行時にも同じ場所を探させる必要がある。標準の検索パス以外に置いた.soを見つけさせるには、環境変数LD_LIBRARY_PATHにディレクトリを追加するのが手軽な方法だ。

$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $ ./app これでカレントディレクトリのlibmathutil.soも検索対象に加わり、実行時のリンクが解決される。手軽だが恒久設定すると意図しないライブラリを拾う事故につながるため、動作確認用の一時的な指定にとどめ、本番の配置は正式な場所とldconfigの仕組みに任せるのが安全だ。

🔍 lddで自分の作った.soを確認する

以前ldd実践で学んだldd(cli-symlink等と同様に既存概念の応用)を、今度は自分が作った実行ファイルにも使ってみる。

$ ldd app linux-vdso.so.1 (0x00007fff...) libmathutil.so.1 => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...) not found と出た場合は、LD_LIBRARY_PATHが通っていないか、そもそもsonameのリンクが張られていないことを疑う。作った側の視点でlddを読むと、配布時に何が足りないかが見えてくる。

⚖️ 静的か共有か、配布の視点で選ぶ

最後に、どちらを選ぶべきかという判断軸を整理しておく。静的ライブラリは実行ファイルが太る代わりに単体で動き、相手の環境を気にしなくてよい。共有ライブラリは実行ファイルを小さく保てる代わりに、実行時にライブラリの所在を解決できる必要がある。

🔗
たとえ静的ライブラリは「工具を全部持ち歩くカバン」、共有ライブラリは「現場に置いてある共用工具を借りる」イメージだ。身軽さを取るか、自己完結を取るかはプロジェクトの配布方針次第になる。

小さなツールを1本だけ配りたいなら静的、同じマシン上で複数のプログラムが同じ機能を使うなら共有、というのが実務でのおおまかな目安になる。次は、この.aや.soを生み出す側のビルドの仕組み、configureやCMakeへと視野を広げる。

この項目に出てくる用語

静的ライブラリせいてきらいぶらり
拡張子.a。arで複数の.oを束ね、リンク時に実行ファイルへコードごと取り込まれるライブラリ形式。
位置独立コードいちどくりつこーど
-fPICで生成される、メモリ上のどのアドレスに配置されても動くオブジェクトコード。
sonameそーねーむ
共有ライブラリの互換性を保つためのバージョン付き内部名。libX.so.1のような形。

関連コマンド

ar

▶ 学習アプリでこの続きを学ぶ・演習する