ZOMI さんに感謝します:https://space.bilibili.com/517221395
GCC のコンパイルプロセスと原理#
GCC の主な特徴
- 移植性のあるコンパイラで、さまざまなハードウェアプラットフォームをサポート
- クロスプラットフォームのクロスコンパイル
- 異なる言語を解析するための複数の言語フロントエンドを持つ
- モジュール設計で、新しい言語や新しい CPU アーキテクチャのサポートを追加可能
- オープンソースのフリーソフトウェアで、無料で使用できる
GCC のコンパイルフロー#
GCC のコンパイルプロセスは、前処理、コンパイル、アセンブル、リンクの 4 つの段階に大別できます。
ソースプログラム(テキスト)#
#include <stdio.h>
#define HELLOWORD ("hello world\n")
int main(void){
printf(HELLOWORD);
return 0;
}
前処理(cpp)#
生成ファイル hello.i
gcc -E hello.c -o hello.i
前処理の過程で、ソースコードが読み込まれ、含まれている前処理指令とマクロ定義がチェックされ、対応する置換操作が行われます。また、前処理の過程ではプログラム内のコメントや余分な空白文字が削除されます。最終的に生成される.i ファイルには、前処理されたコード内容が含まれています。
高級言語コードが前処理を経て.i ファイルが生成される際、前処理の過程ではマクロ置換、条件付きコンパイルなどの操作が含まれます。以下はこれらの前処理操作の説明です:
- ヘッダーファイルの展開:
前処理段階で、コンパイラはソースファイルに含まれるヘッダーファイルの内容をソースファイルの対応する位置に挿入し、コンパイル時にヘッダーファイルで定義された関数、変数、マクロなどにアクセスできるようにします。 - マクロ置換:
前処理段階で、コンパイラはソースファイルに定義されたマクロを使用時に置換します。つまり、マクロ名をその定義内容に置き換えます。これにより、コードの記述が簡素化され、可読性と保守性が向上します。 - 条件付きコンパイル:
#if、#else、#ifdef などの前処理指令を使用して、コンパイル前に特定のコードスニペットが最終的なコンパイルプロセスに含まれるべきかどうかを決定します。これにより、条件付きコンパイルを通じてコードを選択的に含め、異なるプラットフォームや環境でのコード制御を実現します。 - コメントの削除:
前処理段階で、コンパイラはソースファイル内のコメントを削除します。これには、単一行コメント(//)や複数行コメント(/.../)が含まれ、これによりコンパイル速度が向上し、コンパイル後のコードサイズが削減されます。 - 行番号とファイル名の識別の追加:
#line などの前処理指令を使用して、前処理段階でソースファイルに行番号とファイル名の識別を追加し、コンパイルプロセス中にエラーメッセージの位置を特定しやすくします。 - #pragma 命令の保持:
前処理段階で、コンパイラは #pragma で始まる前処理指令を保持します。例えば、#pragma once、#pragma pack などの指令は、コンパイラに特定の処理を指示するために使用され、コンパイラの動作やコードの最適化を制御します。
hello.i
ファイルの一部内容は以下の通りで、詳細は ``../code/gcc/hello.i` ファイルを参照してください。
int main(void){
printf(("hello world\n"));
return 0;
}
このファイルには、ヘッダーファイルが含まれ、マクロ定義 HELLOWORD が文字列 "hello world\n" に置き換えられ、コメントと余分な空白文字が削除されています。
コンパイル(ccl)#
ここでのコンパイルは、プログラムをソースファイルからバイナリファイルに変換する全過程を指すのではなく、前処理されたファイル(hello.i
)を特定のアセンブリコードファイル(hello.s
)に変換する過程を特に指します。
この過程では、前処理された.i ファイルが入力として使用され、コンパイラ(ccl)によって対応するアセンブリコード.s ファイルが生成されます。コンパイラ(ccl)は GCC のフロントエンドであり、その主な機能は前処理されたコードをアセンブリコードに変換することです。コンパイル段階では、前処理された.i ファイルに対して構文解析、字句解析、さまざまな最適化が行われ、最終的に対応するアセンブリコードが生成されます。
アセンブリコードはテキスト形式のプログラムコードであり、次にコンパイルされて.s ファイルが生成され、プログラマーが記述した高級言語コードとコンピュータハードウェアの間の橋渡しを行います。
生成ファイル hello.s
:
gcc -S hello.i -o hello.s
hello.s
:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 6
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
leaq L_.str(%rip), %rdi
movb $0, %al
callq _printf
xorl %ecx, %ecx
movl %eax, -8(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "hello world\n"
.subsections_via_symbols
現在 ``hello.sファイルには完全にアセンブリ命令の内容が含まれており、
hello.c` ファイルが成功裏にアセンブリ言語にコンパイルされたことを示しています。
アセンブル(as)#
このステップでは、アセンブリコードを機械命令に変換します。このステップはアセンブラ(as)によって実行されます。アセンブラは GCC のバックエンドであり、その主な機能はアセンブリコードを機械命令に変換することです。
アセンブラの仕事は、人間が読みやすいアセンブリコードを機械命令またはバイナリコードに変換し、通常は.o というファイル拡張子を持つ再配置可能なオブジェクトプログラムを生成します。このオブジェクトファイルには、行ごとに変換された機械コードがバイナリ形式で保存されています。この再配置可能なオブジェクトプログラムは、後続のリンクと実行の基盤を提供し、私たちのアセンブリコードがコンピュータによって直接実行できるようにします。
生成ファイル hello.o
gcc -c hello.s -o hello.o
リンク(ld)#
リンクプロセスでは、リンカの役割はオブジェクトファイルを他のオブジェクトファイル、ライブラリファイル、スタートアップファイルなどとリンクし、実行可能ファイルを生成することです。リンクの過程で、リンカはシンボルを解析し、再配置を実行し、コードの最適化を行い、スペースのレイアウトを決定し、ロードを行い、動的リンクなどの操作を実行します。リンカの処理を通じて、すべての必要な依存関係が特定のプラットフォームで実行可能なターゲットプログラムにパッケージ化され、ユーザーはこのプログラムを直接実行できます。
gcc -o hello.o -o hello
-v パラメータを追加すると、詳細なコンパイルプロセスを確認できます:
gcc -v hello.c -o hello
- 静的リンクは、リンクプログラム時に必要な各ライブラリ関数のコピーが実行可能ファイルに追加されることを指します。静的リンクを使用して静的ライブラリをリンクすると、生成されたプログラムにはプログラムの実行に必要なすべてのライブラリが含まれ、直接実行できます。しかし、静的リンクで生成されたプログラムはサイズが大きくなります。
- 動的リンクは、実行可能ファイルがファイル名のみを含み、ローダーが実行時にプログラムに必要な関数ライブラリを探すことができるようにすることを指します。動的リンクを使用して動的リンクライブラリをリンクすると、生成されたプログラムは実行時に必要な動的ライブラリをロードする必要があります。静的リンクと比較して、動的リンクで生成されたプログラムはサイズが小さくなりますが、必要な動的ライブラリに依存するため、そうでなければ実行できません。
コンパイル方法#
タイプ | 定義 | 例 |
---|---|---|
ローカルコンパイル | ソースコードをコンパイルするプラットフォームと、コンパイル後のプログラムを実行するプラットフォームが同じであること。 | Intel x86 アーキテクチャ / Windows プラットフォームでコンパイルし、生成されたプログラムが同じ Intel x86 アーキテクチャ / Windows 10 で実行される。 |
クロスコンパイル | ソースコードをコンパイルするプラットフォームと、コンパイル後のプログラムを実行するプラットフォームが異なること。 | Intel x86 アーキテクチャ / Linux(Ubuntu)プラットフォームでクロスコンパイルツールチェーンを使用してコンパイルし、生成されたプログラムが ARM アーキテクチャ / Linux で実行される。 |
GCC と従来のコンパイルプロセスの違い#
従来の三段階の区分は、コンパイルプロセスをフロントエンド、最適化、バックエンドの 3 つの段階に分け、それぞれの段階に専用のツールが担当します。
GCC では、コンパイルプロセスが前処理、コンパイル、アセンブル、リンクの 4 つの段階に分けられています。このうち、GCC の前処理とコンパイル段階は三段階の区分のフロントエンド部分に属し、アセンブル段階は三段階の区分のバックエンド部分に属します。
GCC のリンク段階は三段階の区分のバックエンド部分の最適化段階が統合されたものであり、その目的は実行可能ファイルを生成することに一致しています。
GCC のコンパイルプロセスの 4 つの段階は、従来の三段階の区分のフロントエンド、最適化、バックエンドの 3 つの段階と一定の重複と対応関係がありますが、GCC はコンパイルプロセスをより詳細かつ包括的に区分し、各段階の機能をより明確かつ独立させています。
まとめ#
本節では GCC のコンパイルプロセスについて説明し、主に前処理、コンパイル、アセンブル、リンクの 4 つの段階を含むことを紹介しました。また、GCC の利点と欠点をまとめました:
GCC の利点 | GCC の欠点 |
---|---|
1)JAVA/ADA/FORTRAN をサポート | 1)GCC のコードは結合度が高く、独立して使用するのが難しい。専用 IDE に統合する場合、モジュール化方式で GCC を呼び出すのが難しい。 |
2)GCC はより多くのプラットフォームをサポート | 2)GCC は単一の静的コンパイラとして構築されているため、API として他のツールに統合するのが難しい。 |
3)GCC はより人気があり、広く使用され、サポートが充実している | 3)1987 年から 2022 年までの 35 年間の発展に伴い、後期のバージョンではコード品質が低下している。 |
4)GCC は C に基づいており、C++ コンパイラなしでコンパイルできる | 4)GCC は約 1500 万行のコードを持ち、現存する最大のフリーソフトウェアの 1 つである。 |