.c ファイルはオブジェクト ファイルにコンパイルされ、最終的なバイナリにリンクされます。オブジェクト ファイルは、基本的にバイナリのファイナライズされていない部分です (.c ファイルで定義された関数などのコンパイル済みマシン コードが含まれています)。
.c ファイルは、コンパイル中にヘッダー ファイルをインクルード#include
します。ヘッダー ファイルは、基本的に、ディレクティブがある場所で展開されるだけです。その意味で、.c ファイルはスタンドアロンであり、ヘッダーを個別にコンパイルする必要はありません。それらはすべて、単一のオブジェクト ファイルに変換される単一の翻訳単位の一部です。
コンパイルの最初のステップは、プリプロセッサーを実行することです。これは、 で始まるすべての行を処理する派手なテキスト マニピュレーターです(つまり、ディレクティブや条件付きs など#
の展開を行います)。#include
#ifdef
次に、翻訳単位のテキストがトークン化されます (これは字句解析と呼ばれます)。バイトは、たとえば「.」などの最も単純な認識可能なトークンに変換されます。はドットになり、'++' は単一の 'INCREMENT' になり、キーワードが認識され、変数名はエンティティ全体 (識別子) として解析されます。トークンにはまだ意味がありませんが、バイト ストリームよりも簡単に操作できます。
構文解析と呼ばれる次の論理的なステップでは、トークンのストリームを言語の文法 (構文) に基づいて抽象的な構造に変換します。これは、構文エラーが報告される場所です。たとえば、int a = 3;
declaration(sym(a), expression(consint(3))) として解析される場合があります。
その後の次の論理的なステップは、構文構造に意味を与える意味分析です。たとえば、パーサーは同じ名前で 20 の変数宣言を生成する可能性がありますが、意味的には意味がありません。ここでは、さらに多くのエラーが報告されます。たとえば、すべての制御パスから返されない非 void 関数です。
最後に、低レベルの CPU 命令を選択して翻訳単位のセマンティック構造を実行するコード生成ステップがあります。これは実際には巨大な「ステップ」であり、最終的な命令コードが生成される前に、セマンティック データ構造 (通常は抽象構文ツリーまたは AST の形式) の下位レベル (中間) 表現へのさらなる変換が含まれる場合があります。
実際には、これらのパスの一部が組み合わされます (たとえば、トークン化は通常、構文解析フェーズ中にオンデマンドで行われ、意味的に意味のあるシンボル テーブルなどを構築することもあります)。全体に散りばめられたさまざまな最適化 (一部は統合され、一部は別のパス) もあります。たとえば、GCC はプログラムをSSA中間表現に変換して、データフロー分析を行い、コードをより最適化すると思います。
生成された命令、グローバルおよび静的変数などは、オブジェクト ファイルにダンプされます。オブジェクト ファイルは実行可能ファイルにリンクされます (グローバル変数のアドレス、他の外部オブジェクト ファイルで定義された関数、および動的/共有ライブラリはこの時点で解決され、最終的なコードで修正されます)。
これは gcc に固有のものではありません。これは、ほとんどの (すべての?) C++ (および C) コンパイラに適用されます。