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

ヘッダとライブラリのリンク

標準入出力の printf などは、ヘッダファイル(.h)で関数の宣言を取り込み、実体はライブラリ側にあります。コンパイラは -I でヘッダの探索ディレクトリを追加し、-L でライブラリの探索ディレクトリを、-l でリンクするライブラリ名を指定します。たとえば数学関数 sqrt を使うときは libm の実体を結合するため gcc calc.c -o calc -lm のように -lm を付けます。-l の後ろのライブラリ名は lib と .so/.a を除いた中央部分を書く点に注意します。

Cのプログラムで printf を呼ぶとき、その printf の本体を自分で書いた覚えはないはずです。にもかかわらず動くのは、よく使う機能があらかじめ部品として用意され、それを借りているからです。この借り物の仕組みは、大きく「宣言」と「実体」の2つに分かれています。関数がどんな名前で、どんな引数を取り、何を返すのかという宣言はヘッダファイル(.h)にまとまっていて、#include で取り込みます。一方、その関数が実際にどう動くかという機械語の実体は、ライブラリと呼ばれる別のファイルに収められています。この「宣言は .h、実体はライブラリ」という二段構えを理解することが、ヘッダとライブラリのリンクを使いこなす第一歩になります。

ヘッダファイルの役割

ヘッダファイルは、関数の宣言・マクロ・型定義を記述したファイルです。たとえば stdio.h には printf や scanf の宣言が、math.h には sqrt や sin の宣言が書かれています。ソースの先頭に #include <stdio.h> と書くと、前処理の段階でこの行が stdio.h の中身に置き換わり、コンパイラは printf の正しい使い方(引数や戻り値の型)を知ることができます。書き方には2通りあり、山かっこの #include <stdio.h> はシステムの標準ディレクトリから探し、二重引用符の #include "myheader.h" は自分のソースと同じ場所から優先して探す、という違いがあります。逆に必要なヘッダを include し忘れると、コンパイラは関数の素性を知らないまま処理することになり、引数や戻り値の型を取り違えて警告やエラーの原因になります。重要なのは、ヘッダはあくまで宣言(名前と形)を提供するだけで、関数の実体(中身の機械語)は含んでいないという点です。だから include だけでは足りず、実体のあるライブラリを別途リンクしなければならない場面が出てきます。

ヘッダの探索パスを足す -I

標準的なヘッダ(stdio.h など)は、コンパイラが既定で見る決まった場所(/usr/include など)に置かれており、特別な指定なしに見つかります。しかし、自分で用意したヘッダや、特定の場所にインストールしたライブラリ付属のヘッダは、その既定の場所にはありません。そういうヘッダの置き場所をコンパイラに教えるのが -I オプションです。-I は include の頭文字で、たとえばカレント直下の include フォルダにヘッダがあるなら gcc -Iinclude prog.c -o prog のように、-I に続けてディレクトリ名を書きます。これにより、コンパイラはそのディレクトリも探索の対象に加えます。「ヘッダが見つからない(No such file or directory)」というエラーが出たときは、この -I による探索パスの追加で解決することが多くあります。

ライブラリを結びつける -L と -l

宣言だけでは関数は動きません。リンクの段階で、関数の実体が入ったライブラリを実行ファイルへ結びつける必要があります。ここで2つのオプションが対になって働きます。-L はライブラリファイルを探すディレクトリを追加するオプション(library path)、-l はリンクするライブラリの名前を指定するオプションです。注意すべきは -l の書き方で、ライブラリのファイル名から先頭の lib と拡張子(.so や .a)を取り除いた、中央の部分だけを書きます。たとえば数学関数 sqrt の実体は libm という共有ライブラリにあり、ファイル名は libm.so です。これをリンクするには、lib と .so を除いた m だけを使って -lm と書きます。

具体例 数学ライブラリのリンク

実際に sqrt や pow といった数学関数を使うプログラム calc.c を考えます。ソースの先頭で #include <math.h> をしてコンパイルしても、gcc calc.c -o calc とだけ打つと undefined reference to `sqrt' のようなリンクエラーが出ることがあります。これは「sqrt の宣言は math.h で読めたが、その実体をリンクできていない」という状態で、まさにライブラリの指定漏れです。正しくは gcc calc.c -o calc -lm のように -lm を付け、libm の実体を結合します。これで sqrt の実体がつながり、実行ファイルが完成します。なお、リンクの順序にも作法があり、-lm のようなライブラリ指定はソースやオブジェクトファイルより後ろに書くのが安全です。前に書くと、環境によっては未定義参照が解決されないことがあります。

よくある失敗と実務での使いどころ

つまずきどころは2つに集約されます。1つは、ヘッダは include したのにライブラリの -l を付け忘れて undefined reference になるパターン。printf のように標準Cライブラリにある関数は自動でリンクされるので -l 不要ですが、libm のように分かれているものは明示が要ります。もう1つは、-l の名前に lib や .so を付けてしまう間違いで、-llibm や -lm.so ではなく -lm が正解です。実務では、画像処理の libpng なら -lpng、暗号の libssl・libcrypto なら -lssl -lcrypto、圧縮の libz なら -lz というように、使う機能ごとに対応するライブラリを -l で足していきます。ヘッダとライブラリが特殊な場所にあるときは -I と -L をセットで指定するのが定石で、たとえば gcc -Iinclude -Llib prog.c -o prog -lfoo のような形になります。共有ライブラリの解決状況は、できあがった実行ファイルに対して ldd を使えば確認でき、ライブラリのアーカイブを作る・調べるには ar が使えます。「宣言は -I でヘッダを、実体は -L と -l でライブラリを」と整理して覚えておくと、リンクまわりのエラーに迷わなくなります。

この項目に出てくる用語

ヘッダファイルへっだふぁいる
関数や定数の宣言をまとめた、#include で取り込む .h ファイル。
リンクりんく
複数のオブジェクトファイルやライブラリを結合し、1つの実行ファイルを完成させる工程。
共有ライブラリきょうゆうらいぶらり
実行時に読み込まれる、複数プログラムで共用できるライブラリ(.so)。

関連コマンド

gcclddar

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