MCL(81)

インデックスに戻る

前のMCLへ  次のMCLへ

▼ My Computer Life (81)

81. make

 5月のイベント(フェスタ68)にサークル参加するかもという新たな目標ができた事、また私が爆風演出など一定の技術的課題について解決した事などから、99年1月以降はまた開発の速度がアップして行った。

 このG1プロジェクトで、私はC言語の大きなプログラムを初めて開発する事になったが、やはりというか、かなり初期のうちからmakeを使い始める事になった。以前、「地獄のエイリアン伝説2」などを開発していた時は、makeなど一切使っていなかったのだ。
 makeというのは、UNIXの開発文化の中で作られた、依存関係を考慮してビルドを行うツールである。

 普通にC言語で何かプログラムを作りたいと思えば、C言語のソースファイル(.cという拡張子が一般的)と、ヘッダファイル(.h)をコンパイルすると、何らかの実行形式が出てくる。まずはそれで完成だ(厳密にはアセンブル、リンクという工程も必要になるが)。

 だが、ソースファイルやヘッダファイルの数が多くなってくると、考えなければいけない事が出てくる。
 それが3〜4つくらいならいい。全部まとめてバッチファイルでコンパイルすればいい(このように、複数のファイルをコンパイルなりアセンブルなりして、実行できるファイルを得る事を、ビルドと言う)。実際私はこれまでそうしていた。
 しかし、一般的には、数十〜数百のファイルを使って開発する。そうなった時、「全てコンパイルする」は非常に時間がかかる。今現在のPCではたいした事ないかも知れないが、昔はコンパイルとはとても時間のかかる処理で、全部のファイルをコンパイルするとなると、1日がかりになる事もありうる。
 そこで、誰もが思いつくだろうが、「更新のあったファイルだけコンパイルし直せばいいよね」という事になる。たいていの場合、1〜2ファイルいじったところでまた実行してテストしたくなるのだから、関係するファイルだけコンパイルし直せば良いという事になる。更新したかどうかはタイムスタンプで分かるから、最終生成物であるファイルより更新時間が新しいソースファイルがあれば、それを再コンパイルすればいい。

 と、ここまでは単純な話だが、問題は「依存関係」である。
 例え開発者が更新したのがたった1ファイルであっても、実際1回何かをコンパイルし直せばビルドが終了するとは限らない。
 例えば、その更新したファイルというのが、何かの関数を定義(宣言)しているファイルだとしよう。その場合、変更の内容にもよるが、その関数を呼び出しているソースファイル全て再コンパイルし直さないといけない可能性がある。もっと端的な例で言うと、ある変数を宣言したが、その変数名は一切変えずに、型だけ変えたとしよう(例えばint型からdouble型に)。その場合、名前が変わらないのでソースコード上では何も変わらなくても、最終的に機械語になる時には違う動作をする事になるので、その変数を使用しているソースコードは全部コンパイルし直しである。
 となると、あるファイルの変更が、どのファイルとどのファイルに波及するか(これを依存関係と言う)、それを何かを使って設定しないといけない。そのためのツールが、makeである。

 makeは、Makefileと呼ばれるテキストファイルに書いてある設定を元に、依存関係を調べ、makeが実行された時に何の動作を実行するか決める。例えば、Aという実行ファイル(あるいは中間ファイル)は、BとCとDというファイルから生成される、というような情報をMakefileに書いておく。その上でmakeを実行すると、もしBかCかDが更新されていて、Aよりタイムスタンプが新しいと、Aを生成するためのコマンドを実行する。
 make自体には、コンパイルの機能も何もなく、間接的にコンパイラを呼び出すためだけのツールなので、そのAというファイルを生成するとなったら何を実行すればいいのかも、Makefileに全て書く。逆に言えば、Aを生成するのにはCコンパイラが必要で、しかも特殊なオプションが必要という事になっても、それは柔軟に対応できる。

 このように、依存関係や、生成するためのコマンドを全てMakefileに書かないといけないので、かなり面倒ではある。
 もし、対象がC言語だけだったら、依存関係を抜き出すのはそんなに難しくない。大体、ソースコードからはincludeという形でヘッダファイルが内包される事になるので、includeを追えば何が何に依存しているか分かる。実際、このようにして自動的に依存関係を記述するツールはいくらでもある。
 ただ、makeは間接的にコマンドを呼び出すだけのツールなので、逆にC言語以外でもなんでも使えるようになっている。それこそPascalだろうがJavaだろうが、ビルドというような作業が必要な処理系ならなんでも使う事ができる。なので、別に依存関係の記述も言語に依存しておらず、自動化もしていない。あくまで普通は手で書かないといけない。
 また、makeが対象としているのはプログラミング言語だけではない。例えば、画像ファイルをパッケージングしないと実行ファイルが作れないというようなプロジェクトの場合、画像ファイルの更新があれば再パッケージするなどという処理も書ける。あとは良くある話だが、データファイルの更新に合わせて、データ生成プロセスも走らせるというような事もできる。

 makeをうまく使うと、最小限の処理でビルドが終わるので、開発速度向上に大変効果がある。
 ただ、私はG1では、あまりうまく使えていなかった。
 普通、一般的にはC言語の場合はソースファイル(.c)とヘッダファイル(.h)を1対1で作る。xxx.cとxxx.h、yyy.cとyyy.hという感じで。で、xxxにある関数を使うソースはxxx.hをインクルードし、yyyにある関数を使う時はyyy.hをインクルードする。大体こうやって運用する。
 私はそのへんを把握していなかったので、変数や関数の種類によってヘッダをまとめてしまった。従って、.cファイルと.hファイルは1対1対応していない。この状態だと、.cは多くの.hファイルをインクルードするし、.hは多くの.cファイルにインクルードされるという、多対多の絡み合いができてしまう。
 これを依存度の高いプログラムと言い、一つのファイルの影響範囲が大きくなってしまう。その結果、makeの時間がかかってしまう。それだけならばまだしも、影響範囲が大きくなるという事は、バグが追いづらくなる事を意味している。ある変数が不正に書き換わっているというバグを追う時に、依存性の少ないプログラムなら、追う場所は少なくなる。依存性が高いと、調査しなければいけない範囲も大きくなる。これは仕様変更や修正を加える時も同じ事である。
 このような事は、この後しばらく経ってからわかってきた。

 なお、私は職業プログラマになってからも、makeを完全には理解できなかった。仕事柄使う場面はかなりあったのだが、makeの文法はかなり不思議で、良く分からなかったので、私はいつも誰か(あるいは過去の自分)が作成したMakefileを少しカスタマイズして使っていた。あれを一から書ける人は凄いと思う。


前のMCLへ  次のMCLへ

戻る

TOM 2016