C言語のコンパイルとリンクの仕組み

翻訳環境と実行環境

C言語の実装には、必ず二つの独立した環境が存在する。

  • 翻訳環境:ソースコードを機械語の命令に変換する段階
  • 実行環境:生成された実行ファイルが実際に動作する環境

翻訳環境の処理フロー

Cプログラムは、複数の .c ファイルから構成されることが一般的である。これらのファイルはそれぞれ独立して処理され、最終的に一つの実行可能ファイルに結合される。

  • 各 .c ファイルは、コンパイルプロセスを通じて対応するオブジェクトファイル(Windows: .obj、Linux: .o)に変換される
  • これらのオブジェクトファイルと、必要なライブラリ(標準ライブラリやサードパーティ製ライブラリ)がリンクされ、最終的な実行ファイルが生成される

コンパイルプロセスはさらに三段階に分解できる:

  1. プリプロセッシング(前処理)
  2. コンパイル(言語翻訳)
  3. アセンブル(アセンブリ言語から機械語への変換)
プリプロセッシング

この段階では、ソースファイルとそのインクルードするヘッダファイルが処理され、拡張子 .i の中間ファイルが生成される。

gcc -E source.c -o source.i

主な処理内容:

  • #define マクロをすべて展開し、削除
  • #include 指令を再帰的に解決し、対応するヘッダの内容を挿入
  • #if#ifdef#else#endif などの条件コンパイルを評価して、不要なコードを除去
  • コメントをすべて削除
  • 行番号とファイル名情報を追加(デバッグ情報の生成に使用)
  • #pragma 指令は保持し、後段のコンパイラが利用

この段階が終了すると、ソースファイルにはマクロやインクルード指令は一切存在せず、単一の拡張されたコードの塊となる。エラーの原因がマクロ展開やヘッダの包含ミスである場合、この .i ファイルを確認することで問題を特定できる。

コンパイル

プリプロセス済みの .i ファイルを、アセンブリ言語(.s ファイル)に変換する。

gcc -S source.i -o source.s

この段階では、以下の処理が連続して行われる:

  • 字句解析:文字列をキーワード、識別子、リテラル、演算子などの「トークン」に分割
  • 構文解析:トークン列を構文木(AST)に変換。式や文の構造を階層的に表現
  • 意味解析:型の整合性、変数の宣言・使用の妥当性、型変換の可能性などを静的に検査。型ミスマッチや未定義変数の使用など、構文的には正しいが意味的に誤ったコードを検出

この段階で発生するエラーは「コンパイルエラー」と呼ばれ、実行前に必ず修正が必要である。

アセンブル

アセンブリ言語の命令を、対応する機械語命令に直接変換する。1行のアセンブリ命令はほぼ1対1で機械語命令にマッピングされる。

gcc -c source.s -o source.o

この段階では最適化は行われず、純粋なバイナリコードへの変換のみが目的である。生成される .o ファイルは、CPUが直接実行できないが、リンク可能な形式の機械語である。

リンク

複数のオブジェクトファイルとライブラリを統合し、一つの実行可能ファイルを生成する。

主な処理:

  • アドレス割り当て:各オブジェクト内の関数や変数に、実行時のメモリ上の物理アドレスを割り当てる
  • シンボル解決:複数のファイル間で参照されている関数・変数(例:main() が呼び出す printf())の実体を特定
  • リロケーション:参照先のアドレスを実際のメモリ位置に置き換え、ジャンプ先やデータアクセスを修正

たとえば、a.c で定義された関数 calculate() を b.c から呼び出している場合、リンク時にその関数の実際のアドレスが b.c の呼び出し箇所に埋め込まれる。

実行環境

生成された実行ファイルが動作する環境では、以下のステップが順に実行される:

  1. OSによってプログラムがメモリに読み込まれる(スタンドアロン環境では手動でロードされる場合もある)
  2. プログラムのエントリポイントである main() 関数が呼び出される
  3. 実行中、関数のローカル変数や戻りアドレスはスタックに格納され、グローバル変数や静的変数は静的メモリ領域に保持される
  4. main関数が正常終了するか、異常終了(例:セグメンテーションフォールト)によりプログラムが停止

タグ: C 編譯 リンク プリプロセッサ アセンブル

7月1日 23:35 投稿