Pset5に合わせたコンパイラ考察

hash関数のhash_itをdictionary.hに定義したとする。複数の.c fileでheader file (今回は、dictionary.h)をincludeすると...
コンパイラはhash_itを2つ生成してしまう。(☆)

その理由を詳しくみてみる。コンパイルという過程は以下の4つからなる:
1) プリプロセッサがまず走る (= プリプロセッシング)
#defineが目的の単語を置換したり、#includeが展開など諸々の処理がなされる。
includeの中身はワッと.c fileの一番上に乗ってくる。
(= dictionary.cとspeller.c両方でinclude内のdictionary.hが展開される)

2) compilation (= コンパイル)
.c fileを.oにする
3) linking (= リンク)
複数の.oを繋ぐ。
.cが複数あると
プリプロセッサコンパイル→.oになってからリンクされ走る。
.oになった段階では1つのファイルに関数が足りていなくても大丈夫。
例えば、speller.cとdictionary.cがあるとき
speller.oとdictionary.oができ、
この2つの結合をmakeが行う。
もっと言うと...speller.oはload関数を持っていないが、speller.oがload関数を欲しがっているという情報は存在する。(実行する為にはこの関数が必要です、という一覧は入っている)

3)linkは「コンパイル後、.oを寄せ集め、足りない関数を補い合わせ(他の.oから)」、4)プログラムとして動かす。
4) プログラムが走る

(☆)に戻ると、
2つの.oは独立に作られている→hash_itが2つ出来てしまい、リンクの時に失敗する。

解決策は...

  • static 関数を利用する

static を接頭語とし関数を宣言すると、そのファイル中だけで使える関数となる。感覚としてはlocal 変数と同じ。ファイルローカル関数と言っても良い。同じ内容だが各々のファイルにおけるローカル関数として扱われる。これなら、リンクの時に他の.oに同じ関数名があっても大丈夫。
今回のケースと同様の理由で、ヘッダに関数を定義するときはstaticを付けるという慣習がある。
欠点は、無駄にメモリを食うこと。
(ファイルサイズはあまり速度に関係無いため、今回の課題ではstatic関数として定義し、重複しても問題ないだろうけど)

  • hash_itをdictionary.cで定義する

dictionary.cの上方(load, check, size, unloadの外)にglobal 関数として定義すればOK。
link後は、dictionary.c・speller.cの2つがもともと何のファイルだったか関係なく完全に融合している・溶け込むためdictionary.cに1度のみ定義していればよい。
speller.cでloadを使いたいときは...
①speller.cでload関数を宣言していないと、load関数の型がわからない→エラー
(宣言しなかったらloadがそもそも関数なのかどうかコンパイラが知らない、そんな名前知らないよふええとなってしまう)
②宣言さえしていればloadは関数だということをコンパイラは知っている→その中身がどうなっているか知らなくてもコンパイル自体は出来て、リンクするときに別ファイルにでもloadの中身が含まれていればOK