2021年12月3日金曜日

最小の makefile を作りたい(改訂版)

小さいCプログラムのための makefile を作りたい、という話。
 

カレントディレクトリに main.c という簡単な C プログラムがあったとき、これをコンパイルして main という実行ファイルを作ることを考えます。 まあ cc main.c -o main で終わり、なので、

てことになるわけですが、やれここでコンパイル時にデバッグフラグだの、数学関数をリンクするだの言い始めると途端にオプションが増えます。

そんなときによく使われるのが makefile に書かれる変数なのですが、これもまた雑に調べるとオプションを指定する環境変数がごちゃごちゃしていて分かりにくい。 特に LDFLAGS と LDLIBS の扱いで混乱してしまい、困ってしまいました。 リンクするライブラリ(-lmなど)はソースファイルの後に書かなければいけないのですが、 LDFLAGSに書けばいいよみたいな資料が若干見当たり、その通りに書くとコンパイルできない(リンクに失敗する)という…

こんなとき役に立つのが make のデフォルトルールの表示です。 Linux の make (GNU make) には オプション -p があるので、
であることが分かります。一方、FreeBSDのmake (BSD make) はオプションと挙動が違いまして、 となります。このデバッグ出力は標準エラー出力に吐き出されますので注意してください。

いずれにせよ、
  • LDFLAGSはソースファイルより前に付く
  • LDLIBSはソースファイルより後に付く
  • .c ルールはソースファイル foo.c から実行ファイル foo を作る、というルールなので、program: foo.c などとだけ makefile に書いてもルールに合致しないので何も起こらない
ことが分かりますから、
  • LDFLAGS にはライブラリの所在を表すパス(-L オプション)などを書く
  • LDLIBS にはリンクしたいライブラリの実体なり -l オプションなりを書く
という使い方であることに気づくわけです。

オブジェクトファイル .o を作る場合はどうなるのか? については、ルールの表示から探してみて、CFLAGS の付けかたについて考えてみるとよいでしょう。例えば、プログラム中で自作関数などが呼び出された回数や処理時間を計測するプロファイラ gprof を用いる場合は、コンパイラとして gcc を想定し、コンパイル時とリンク時の両方に -pg オプションが必要なので、たとえば
という書き方になります。main()があるソースコードのファイル名は拡張子をつけずに実行ファイル名とお揃いにしておく(prog.c に main()を書くなら実行ファイル名は prog とする)というのがポイントです。 これは、make -pで表示される暗黙のルールから、以下の二つが適用されて、main という実行ファイルが作られる、ということから確認できます。
(FreeBSDでは cc が clang の時に -pg オプションを付けると、正しく動作しない実行ファイルが出来て困ったので、念のため書き添えておきます。 )

gmake には .c.out ルールが標準で備わっていません。さらに、実行ファイルを作成するルールについても、よく見ると となっており、実行ファイルに .exe とか .out をつけるようにはなっていません。実行ファイルのファイル名と、ソースコードなどのファイル名が異なるもの(sample という実行ファイルを main.c から作る、みたいな)だと途端にうまくいかなくなるわけです。また、この様子だと分割コンパイルを行う場合も、暗黙のルールには頼りにくいように見えます。

上記の点を踏まえ、さらに分割コンパイルを行うときのMakefile は、たとえば以下のようになろうかと思います。 main()関数を持つ main.c, 他の関数を持つ sub.c, sub.c が持つグローバル変数や関数プロトタイプの情報を他の関数に伝えるための sub.h から、 実行ファイル prog を作る例です。

EXEC が実行ファイル名をあらわす変数ですが、これが prog ではなく main なら、OBJS から EXEC を作る部分の LINK.o のルールは、main.o から main を作る暗黙のルールが適用となるため、不要となるでしょう。さらに、 main.o や sub.o の作成については暗黙のルールを適用できそうな気がします。これらのことを大胆に(ヘッダファイルの依存関係を省略するなどして)反映させると、上のMakefileは下のように若干短縮出来ます。
rm -fの後ろにアスタリスクを含めるのはうっかりミスで全てのファイルを消し飛ばすくらいのハイリスクなのでやめておいたほうがいいと思いますし、ソースコードの依存関係が分かりにくいので、しっかり書いたほうがよいでしょう。