ヘッダとライブラリのリンク
標準入出力の 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 と書く。
なぜこの省略形が必要なのかは、次に実際のエラーで体感してみよう。
🧪 具体例 math.h を include したのにリンクで叱られる
実際に sqrt や pow といった数学関数を使うプログラム calc.c を考える。
ソースの先頭で #include <math.h> をしてコンパイルしても、gcc calc.c -o calc とだけ打つと undefined reference to `sqrt' のようなリンクエラーが出ることがある。
include したのに叱られるのは、「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 でライブラリを」と整理して覚えておくと、リンクまわりのエラーに迷わなくなる。