出力ファイル名を指定 -o
gccを使ってコンパイルした実行バイナリファイルのファイル名は、そのソースファイルがhello.cであってもmyprog.cであっても、すべてa.outという名前になります。このファイル名を変更するには、-o 出力ファイル名というオプションを使用します。
通常は、hello.cをコンパイルすれば、同じ名前のhelloという実行バイナリファイルができるようにするとよいでしょう。つまりhello.cの場合は-o helloです。
具体的なコマンドラインは、
となります。これで、helloというファイル名の実行バイナリファイルができるので、これをhelloというコマンド名で実行できます。実際にはカレントディレクトリのPATHを指定するため「./」を頭につけて実行し、
となります。
このhelloというファイルをPATHの通ったディレクトリにインストールすれば、一般のコマンドと同様に、頭の「./」を付けずにhelloとだけ入力すれば実行できるようになります。
最適化オプション -O
gccの出力は、デフォルトでは最適化が行われていません。hello.cのような数行の簡単なプログラムでも、最適化せずにコンパイルしたものをアセンブラレベルで見ると、無駄な命令が多く含まれています。
gccではコマンド行に「-O」オプションを追加することによって最適化を行うことができます。この「-O」オプションにもいくつかの最適化レベルがありますが、多くの場合、最適化レベル2の「-O2」オプションを用いるのがよいでしょう。
ここで説明した出力ファイル指定オプションと最適化オプションをまとめて、コンパイル時の標準的なコマンドラインは、
となります。
なお、gccの最適化がどの程度行われているかは、プログラムの実行速度を計ってみる方法のほか、実行バイナリファイルのサイズをsizeコマンドで確認したり、コンパイルをアセンブラレベルで止めてそのアセンブラのソースを比較してみたりする方法があります。
コンパイルの段階
C言語のソースから実行バイナリを作成することをコンパイルと呼んでいますが、このコンパイルは、
- プリプロセッサの処理
- 狭義のコンパイル
- アセンブル
- リンク
という4つの過程から成り立っています(図3.1)。
gccを起動すれば、これらの4つの過程が自動的に実行されます。gccコマンドは、実はコンパイラドライバと呼ばれるプログラムであり、gccコマンド自体はコンパイルなどの実作業を何も行わず、gccの内部から実際にコンパイル・アセンブル・リンクなどを行うコマンドを呼び出すことによって一連のコンパイル動作を行っているのです。
プリプロセッサ
プリプロセッサでは、「#include」「#define」といった行頭に「#」記号のあるプリプロセッサの制御行が展開されます。
C言語では、プリプロセッサの部分はコンパイラ本体から切り離されており、コンパイラ本体は「#include」や「#define」については何も知りません。コンパイラ本体には、あくまでプリプロセッサを通った展開済みのソースコードが渡されることになります。
たとえば、前述のhello_define.cのように「#define MESSAGE "Hello World\n"
」と定義されている場合、コンパイラ本体には"Hello World\n"
という文字列のみが渡され、コンパイラ本体はその文字列がもともとMESSAGEとしてdefineされていたということを知りません。
コンパイラ本体
コンパイラ本体は、プリプロセッサから渡されたC言語のソースコードをアセンブラに変換します。この部分がコンパイラの中心部分です。同じC言語のソースコードでも、なるべく簡潔で高速なアセンブラのソースコードを出力するようなコンパイラほど、優れたコンパイラであると言えます。
アセンブラ
アセンブラのソースコードをアセンブルして、CPUの命令コードを含んだバイナリファイルを出力します。このバイナリファイルは、直接実行可能なバイナリファイルではなく、次のリンカにかけられる前の、リロケータブルオブジェクトファイルです。
リンカ
リンカは、アセンブラが出力したリロケータブルオブジェクトファイルを、libcやそのほかの必要なライブラリおよびCランタイムルーチン(crt*.o)とリンクして、実行可能なバイナリファイルを出力します。
hello.cのような簡単なプログラムではソースファイルは1つだけでしたが、プログラムが複数のソースファイルに分割されている場合は、複数の*.oファイルをリンカの段階で1つのバイナリファイルにまとめます。