実行ファイルの構築



実行ファイルを作るまでの流れ

C言語のソースファイルから最終的には実行ファイルを作りたいわけですが、その流れをしっかりと把握しておくことはとても大切です。特にVisual-C++などの統合開発環境ではある程度ビジュアルに勝手に構築してくれます。これは大変便利なのですがその反面、基本概念を知っていないと何がなんだか訳がわからないままプログラムを書く羽目になってしまいます。開発環境に任せきりにせずに影でこそこそ何が行われているのかしっかり把握しましょう。
 C言語の構築(make)の大まかな流れは下図の通りです。



 まずは概略だけ見ておきましょう。C言語のソースをプリプロセッサで処理し、コンパイラによりオブジェクトファイルに変換。続いてリンカにより他のオブジェクトファイルやライブラリを結合して実行ファイルを生成します。
 なお余談ですが、狭い意味でC言語と言った場合これはCプリプリセッサとCコンパイラのことを指します。つまりC言語のソースファイル1つから1つのオブジェクトファイルを構築するまでが、「C言語」のお仕事です。まあ普通のCの処理系であればそこから実行形式のファイルを生成するためのツールは全部ついていますが、別にリンカやライブラリアンが付属していなくてもフルセットのC言語と言ってかまいません。なぜならリンクから先の作業は他のコンパイラ言語と共有できるからです。実際問題としてアセンブラやPascalなどのコンパイラの出力するオブジェクトファイルとリンクするなどということはあります。



ソースファイル

 DOSやWindowsではC言語の場合は ".c"、C++の場合は ".cpp" の拡張子を使います。ようするにC言語で書かれたテキストファイルで、プログラマが毎日せっせと書いているファイルです。



ヘッダファイル

 拡張子に ".h" を使います。ヘッダファイルには2種類あり、1つは < > でインクルードするシステムに登録されたディレクトリにあるものと、" " で読みこむソースファイルと同じディレクトリにあるユーザーが用意したものです。前者は stdio.h や math.h などでお馴染みでし、後者もモジュール間で情報を共有するためにしばしば作成する必要に迫られます。
 いずれにせよ、複数のソース間で共有する情報をヘッダファイルにして各ソースからインクルードすることにより効率よく開発を行うことを目的としたものです。



プリプリセッサ

 C言語のソースファイルに対して文字列的な処理を行います。主にコメントの削除、ヘッダファイルのインクルード、マクロの展開などを行います。



前処理済みソースファイル

 プリプリセッサにより処理されたファイルです。通常はコンパイラマネージャーのようなツールを用いてプリプリセッサとコンパイラがまとめて呼び出されますので実体を見かけることはあまりりありませんが、オプション指定で吐かせることは出来るはずです。興味があれば覗いてみてください。通常は拡張子 ".i" のようです。



コンパイラ

 前処理済みのソースファイルをコンパイルしてオブジェクトファイルに変換します。この段階でプログラムは機械語に翻訳されるわけですが、ラベルや変数などのメモリ上のアドレスは変換されず、名前情報のままオブジェクトファイルに組み込まれます。



オブジェクトファイル

 このファイルは基本的には機械語のプログラムとなっているためにどのような言語から生成されたものであってもかまいません。ただし、オブジェクトファイルは実行時に必要である「プログラムのどの部分をメモリのどの番地に格納すれば良いか」などの情報がありません。しかしアドレスの代わりにそれらには名前が割り振られており、「○○とい名前のモジュールのどの部分が××という名前の変数を読みにいく」だとか「△△という名の変数が他のモジュールにあるはずだから探してくれ(extern)」だとか、「□□は外からアクセスしてもいいよ(public)」とかいろんな情報が組み込まれています。



ライブラリファイル

 基本的な情報はオブジェクトファイルと同じで、実際オブジェクトファイルからライブラリアンというツールを使って作成します。オブジェクトファイルの場合はそのすべてが実行ファイルに組み込まれるのに対して、ライブラリの場合は必要な部分だけを取り出して使えるような形式になっています。C言語の標準関数のほとんどはこの形式で提供されますし、よく使う関数群などをライブラリに変換して管理することもよく行います。



リンカ

 複数のオブジェクトファイルを結合し、その中で利用しようとしているモジュールをライブラリから検索し必要なものだけを組み込みます。この段階で始めてお互い名前で関連付けあっていたものがメモリアドレスというものにそれぞれ配置され実際に実行できる状態になります。名前空間の処理はリンク時まで行われませんので、もし名前空間にバグがあった場合(extern 宣言した変数の実体がなかったり、スペルミスがあった場合など)リンク時に始めてエラーとなります。コンパイル時のエラーと違いリンク時のエラーは究明が難しい場合が多いので注意が必要です。また、名前の重複があってもデフォルトでは warning を吐かない場合が多いですので注意が必要です。C++ の言語仕様上、正規の使い方で名前の重複がありえるからなのですが(テンプレートとかね)、複数人で開発するときなどたまたま同じグローバル変数名を使ってしまったりするとかなり不可解な現象となってバグが現れます。




C言語でリンクされるものいろいろ

 えっと、訳わかんないこといっぱい書いてしまったので、まとめもこめて、C言語だけで開発するとき通常リンクされるものを確認しましょう。


スタートアップルーチン

 これはオブジェクトファイルの形で供給されます。内容としてはプログラムが起動してから main 関数を呼び出すまでの処理とmain関数からリターンした後の終了処理が入っています。具体的には、後で標準関数を呼び出せるようにいろいろな初期化をします。各変数の初期化やヒープ領域の準備など処理系によってもいろいろですが、とにかくこれをリンクしないことには動きません。通常このファイルはアセンブリ言語によって書かれており必要であれば自作して入れかえることも可能です。


標準ライブラリ

 ひとつの場合もあればジャンル毎に複数ある場合もありますが、通常 printf や abs や strcpy などの標準関数はライブラリの形で提供されます。ライブラリですので、オブジェクトファイルから参照されているものしかリンクされません。



終わりに

 うーむ、また趣味に走って訳わからんことを書いてしまった。本当にあっているんだろうか、オブジェクトファイルに関してはDOS時代のインテルオブジェクトモジュールフォーマットしか調べてないから、x86系以外の開発環境にはそのままでは当てはまらないかも (^^;;
 まあ大体一緒だとは思うんですが......