感谢 up 主 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 万行のコードを持ち、現存する大規模なフリーソフトウェアの一つである。 |