10 月 14 日(水)

コンパイルの自動化

前回は,単一のプログラムを複数のソースファイルから作成してみた. プログラムの規模が大きいほど, ソースファイル分割のメリットも大きくなる. その反面,コンパイル方法が複雑になってしまっていた. 今回は,プログラミングそのものではなく, コンパイル作業について改善してみよう.

Unix では,複雑なコンパイル作業を効率的に実行するためのツール make が用意されている. Makefile という名前のファイルにコンパイル手順を記録しておけば, どんなに複雑なコンパイル作業であっても 単純に make コマンド1発だけで完了できるようになる.


makeMakefile

とにかく,make を使ってみよう. まず準備として,前回作成した統計処理プログラムの分割ソース版 calcstat-2 のディレクトリへ移動しておこう. そして,もしオブジェクトファイル(拡張子 .oプログラムファイル(実行形式)があれば, それらを削除しておこう. なお,誤って ソースファイル .c を消してしまわない よう,注意すること!

$  cd  .../calcstat-2	# 前回のディレクトリへ
$  rm  *.o		# オブジェクトファイルの削除.*.c と間違わないこと!
$  rm  calcstat		# プログラムファイルの削除

最も単純な Makefile の例が List 1 である.

List 1. 最も単純な Makefile
calcstat:							
	cc main.c input.c func.c -lm -o calcstat -Wall	# コンパイルとリンク
List 1 の内容は,ほとんど,一括コンパイルのコマンドそのままだ. なお,Makefile を作成する際には, 先頭の空白は [Tab] であることに注意しよう. [Space] だとうまく行かない.

このような Makefile を用意しておけば, そのディレクトリ内では, 次のコマンドを実行するだけでコンパイルできるようになる:

$  make
または
$  make  calcstat
これで,Makefile 内に記述した cc コマンドが実行される.

ただし,List 1 のような単純すぎる Makefile では, ソース分割のメリットをまったく活かしていない. そこで,より実用的な形式の Makefile を作成してみよう. List 1 の Makefile を元にして, List 2 のように書き換えよう.

List 2. 基本的な Makefile
calcstat: main.o input.o func.o
	cc main.o input.o func.o -lm -o calcstat	# 全オブジェクトのリンク(プログラムの生成)

main.o: main.c input.h func.h
	cc -c main.c -Wall	# 個別のコンパイル(オブジェクトの生成)

input.o: input.c input.h
	cc -c input.c -Wall	#          〃         

func.o: func.c func.h
	cc -c func.c -Wall	#          〃         

.PHONY: clean distclean

clean:
	-rm *.o

distclean: clean
	-rm calcstat
List 2 の内容は,複数のソースファイルを1個ずつコンパイルするためのコマンドラインとほぼ同じだ.

一般に,Makefile は, 次の書式のような複数のエントリ(要素)から構成されている:

ターゲット: 依存ファイル1  依存ファイル2  ...
	ターゲット作成などのコマンド

ターゲット(目標)には,生成したいファイルの名前などを指定しておく. 依存ファイルには, そのターゲットを生成するために必要となるファイルやターゲットを指定しておく.

「ターゲット=製品」,「依存ファイル=材料」と思えばよいだろう.

たとえば,List.2 のターゲット main.o に関するエントリ:

main.o: main.c input.h func.h
	cc -c main.c -Wall

は,次のことを表わしている:

もし,その依存ファイルと同じ名前のターゲットが この Makefile 内に記述されていれば, そのエントリについても芋蔓式(イモヅル式;連鎖的)に実行されることになる. たとえば,最初のエントリ:

calcstat: main.o input.o func.o
	cc main.o input.o func.o -lm -o calcstat

は,オブジェクトファイル main.oinput.ofunc.o を材料にしているので, このエントリ calcstat(複数オブジェクトの連結)を実行しようとすると, その前に,各オブジェクトファイルのエントリ(各ソースファイルの個別コンパイル)が自動的に実行されることになる.

なお,ターゲット名は,ファイル名だけに限らず,何でも構わない. とにかく,次のように書けば, そのエントリのコマンドが実行されることになっている:

$  make  ターゲット名

ただし,ターゲット(オブジェクトファイルなど)が 依存ファイル(ソースファイルなど)よりも新しい場合には, 「すでに実行済みなので,再実行は不要」ということになるので, そのエントリのコマンドは実行されない. 依存ファイルを更新した場合(材料が変わった場合)だけ実行される.

同じエントリを複数回連続実行して確かめてみよう. なお,ターゲットを省略すると, Makefile 内の最初のエントリが実行される.

つまり,make は, 書き換えられたソース(再コンパイルが必要なソース)だけを選別してコンパイルする. ソースを書き換えていない場合には, そのソースのコンパイルを自動的に省略してくれる. 前回説明した通り, 更新していないのに再コンパイルするのは無駄なので.

ところで,List 2 では, ターゲット cleandistclean のエントリだけ, 他とは違う形式になっている:

.PHONY: clean distclean

clean:
	-rm *.o

distclean: clean
	-rm calcstat

オブジェクトファイル .o は, プログラムファイル calcstat の完成後には不要になるので, これらのエントリでは, これらの不要ファイルを簡単に削除できるようにしている. しかし,これらでは,他のエントリとは異なり, clean というファイルを生成しているわけではないので, 更新の有無は判断できないことになる. そこで,ターゲット名 cleandistclean擬似ターゲット .PHONY に指定している. 通常のターゲットとは異なり,擬似ターゲットのエントリは, 更新有無に関係なく常に実行されることになっている:

$  make  clean
または
$  make  distclean

なお,コマンド rm の前の記号「-」は, そのコマンドのエラーを無視するための指定である. すでにファイルを削除済みの場合でも, make を中断しないようにしている.

この記号を削除するとどうなるか?確かめよう.

make の利点

すでに理解しているように,プログラム開発では, ソースの変更とコンパイルとを何度も繰り返す必要がある. make を使えば,コンパイル作業を効率化できる. 主なメリットは次の通り:

では,make による分割コンパイルのメリットを理解するため, いくつか実験してみよう.

実行結果と List 2 とを比べて見れば, 更新されたファイルに関連する処理だけが 選択的かつイモヅル式に実行されることがわかるだろう.

ただし,Makefile 内の記述が不適切な場合には, 無駄や不足も発生し得る.

本日の課題

前回の課題で作成した 分割ソース版 cg プログラムに対して, Makefile を作成せよ. そして,すべてのエントリが正しく動作することを確かめよ.

動作の確認方法:

レポートには,次の内容を記述すること:

レポート提出

レポートに関するルール を厳守.

注意事項

補足

ソース分割(関数分類)の妥当性を検討したり, Makefile を効率よく作るために, すべてのソースファイルおよびヘッダファイルについて, 構成情報を抽出(ちゅうしゅつ)しよう.

分割ソースファイルの構成情報の書式の例:

ヘッダファイル名.h:
	依存:ヘッダファイル名.h, ...	# include した自作のヘッダすべて(標準ライブラリのヘッダは不要)
	宣言:関数名(), ...		# extern 宣言した関数すべて(もしあれば,グローバル変数も)
	定義:型名, ...			# typedef した型すべて

ソースファイル名.c:
	依存:ヘッダファイル名.h, ...	# include した自作のヘッダすべて(標準ライブラリのヘッダは不要)
	定義:関数名(), ...		# 定義した関数すべて(もしあれば,グローバル変数も)

...
宣言」と「定義」の違いに注意.

上級者向け情報

List 2 の Makefile では, 同じようなコマンド(cc -c ソース.c )を3回も羅列していて冗長だった. List 3 では,コンパクトに書き直してみた.

List 3. 無駄の少ない Makefile
...

main.o: main.c input.h func.h

input.o: input.c input.h

func.o: func.c func.h

.c.o:
	cc -c $< -Wall
...

(c) 2015, yanagawa@kushiro-ct.ac.jp