コンパイル→実行可能ファイル
簡単な流れ
GCCを使用します。
C言語でソースコードを記述しコンパイルした時どんな風になっているのか。
「コンパイルしたら動くファイルができる!」
という理解でも問題はありませんが、もっと深く知ることでコンピューターについての理解が深まると思います。
そもそもなぜコンパイルが必要なのか。
コンピューターが理解する機械語は番号などの数値で記述されるため人間が機械語でプログラムを書こうとすると、まず命令を覚えるのに非常に手間がかかる上にミスも増えます。
なのでソースコードは人間にも分かりやすいように人間が理解しやすいC言語などの高級言語で記述します。
そのソースコードを機械にも分かる機械語に翻訳するのがコンパイラの役目となります。
私達がC言語で記述した最終的に電子回路、つまり電圧の高低に変換され計算が施されます。
コンピューターは与えられた入力を命令された回路に流し込んで得られた出力を返しているだけです。
つまりC言語の関数名や変数名は理解しませんしできません。
これをコンピューターに理解できる状態に加工するために数段階に分けて細かく分解しています。
つまりC言語をコンピューターが理解できるように翻訳するのがコンパイラの仕事です。
以下の流れで実行可能ファイルは作成されます。
↓マクロなどの処理
コンパイル
↓アセンブリへの変換
アセンブル
↓機械語(数値の羅列)へ変換
リンク
↓断片化した機械語のファイルをまとめる処理
実行可能ファイル
※この時コンパイラはファイルひとつづつファイルをコンパイルしていきます。
以下細かく見ていきます。
プリプロセス
コンパイルを行うソフトウェアが処理を行う前の準備段階で行われる処理でソースコードに書かれたプログラムの本質的な処理にかかわらない処理を実行させます。
マクロによる文字列の置き換え処理やファイルの読み込みなどが主な処理でソースコードに記述でき、それらの命令は「プリプロセッサディレクティブ」と呼ばれます。
#include、#define、//コメント、/*コメント*/
cc -E ファイル名
これでプリプロセスの時点で処理を停止しプリプロセス済みのソースコードを出力します。
コンパイル
プリプロセスによる処理が終了すると今度はコンパイラによって高級言語をアセンブリ言語へ変換します。
アセンブリとは機械語命令とと一対一に対応したニーモニックで記述する言語でレジスターを直接操作したり、アドレスの番地を直接指定できるなどC言語といった高水準言語がプログラマーの代わりにやってくれていたことをプログラマー自ら行うことができ、より細かくコンピューターの操作を行うことができます。
ただし機械語と一対一に対応する文法を持っているだけで、まだ人間の為の言語なのでこれを機械語に変換する必要があります。
次のアセンブルに備えた準備段階です。
gcc -S ファイル名
gccに-Sオプションを付加することでコンパイルの時点で停止できます。
アセンブル
上記のコンパイルで得られたファイルを機械語に変換するのがアセンブルです。
この時点でソースコードは1と0のバイナリコードに変換され、これをオブジェクトファイルと呼びます。
しかしこの時点ではまだファイルを実行することはできません。
gcc -c ファイル名
リンクの前までは行いません。
リンク
アセンブルの段階でコンピューターが理解することのできるオブジェクトファイルは出来上がりましたが、まだ実行することはできません。
既述したようにコンパイラはファイルをひとつづつコンパイルしオブジェクトファイルを生成していきます。
他のファイルと結び付けられている変数や関数を参照する場合、そのことを通知しなければなりません。
外部のファイルスコープを参照する場合はextern修飾子を用いてコンパイラに通知します。
こうしてオブジェクトファイル同士を結び付ける作業がリンクになります。
これを行うソフトウェアのことをリンケージエディタやリンカーと呼んだりします。
関数名などはシンボルと呼ばれ、シンボルはシンボルテーブルにより実際のメモリアドレスと結び付けられます。
ここで晴れて実行可能ファイルが完成するわけです。
普通ファイルは複数に分割して作成します。
その時の方法は以下。
分割コンパイル
複数のオブジェクトファイル、Test.h,Test.c,main.cに分割してソースコードを作った場合、ひとつづつファイルをコンパイルしオブジェクトコードを生成しそのオブジェクトファイル同士をリンクします。
gcc ファイル名 ファイル名 ファイル名
3つのファイルをリンクして一つのファイルにして処理が完了します。
Test.h
#include
void func(void);
Test.c
#include "Test.h"
void func(void){
printf("hello world");
}
main.c
#include "Test.h"
int main(void){
func();
}
まずは3つに分割したファイルをコンパイルし「ファイル名.o」のオブジェクトファイルを3つ作ります。
後はgccでリンクするだけです。
コンパイルすると言ってもこれだけの処理をコンパイラが代わりに行ってくれています。
gccは様々な命令をひとまとめにしたコマンドだったんですね。
コメント