コンパイラのしくみ
1. 序章
プログラミング言語は、開発者が人間が読めるソースコードを記述できるようにするために作成されました。 ただし、コンピュータはマシンコードで動作するため、人々はほとんど読み書きできません。 したがって、コンパイラは、プログラミング言語のソースコードを特定のマシン専用のマシンコードに変換します。
この記事では、コンパイルプロセスのフェーズを分析します。 次に、コンパイラとインタプリタの違いを見ていきます。 最後に、最新のプログラミング言語のコンパイラの例をいくつか紹介します。
2. コンパイルフェーズ
すでに述べたように、コンパイルプロセスは、高レベルのソースコードをターゲットマシンで実行できる低レベルのマシンコードに変換します。 さらに、コンパイラの重要な役割は、コミットされたエラー、特に構文関連のエラーについて開発者に通知することです。
コンパイルプロセスは、いくつかのフェーズで構成されています。
- 字句解析
- 構文解析
- セマンティック分析
- 中間コード生成
- 最適化
- コード生成
このセクションでは、各フェーズについて詳しく説明します。
2.1. 字句解析
コンパイルプロセスの最初の段階は字句解析です。 このフェーズでは、コンパイラはソースコードを語彙素と呼ばれるフラグメントに分割します。
String greeting = "hello";
上記のステートメントには、5つの語彙素があります。
- 弦
- 挨拶
- =
- “こんにちは”
- ;
コードを語彙素に分割した後、トークンのシーケンスが作成されます。 トークンのシーケンスは、字句解析の最終結果です。 したがって、字句解析はトークン化とも呼ばれます。 トークンは、語彙素を説明するオブジェクトです。キーワード、変数名、文字列リテラルなど、語彙素の目的に関する情報を提供します。 さらに、語彙素のソースコードの場所データを保存します。
2.2. 構文解析
構文解析中、コンパイラーは最初の段階で生成された一連のトークンを使用します。 トークンは、プログラムの論理構造を表すツリーである抽象構文木(AST)と呼ばれる構造を作成するために使用されます。
このフェーズでは、コンパイラはソースコードの文法構造とその構文の正確さをチェックします。一方、構文エラーがあるとコンパイルエラーが発生し、コンパイラは開発者にそのことを通知します。
簡単に言うと、構文解析は2つのタスクを担当します。
- ソースコードに構文エラーがないかチェックします。
- 次のステージで使用する抽象構文木を生成します。
2.3. セマンティック分析
この段階では、コンパイラは抽象構文ツリーを使用して、セマンティックエラーを検出します。例:
- 変数に間違ったタイプを割り当てる
- 同じスコープ内で同じ名前の変数を宣言する
- 宣言されていない変数を使用する
- 言語のキーワードを変数名として使用する
セマンティック分析は、次の3つのステップに分けることができます。
- タイプチェック–割り当てステートメント、算術演算、関数、およびメソッド呼び出しでタイプの一致を検査します。
- フロー制御チェック–フロー制御構造が正しく使用されているかどうか、およびクラスとオブジェクトが正しくアクセスされているかどうかを調査します。
- ラベルチェック–ラベルと識別子の使用を検証します。
上記のすべての目標を達成するために、セマンティック分析中に、コンパイラーは抽象構文ツリーを完全にトラバースします。 セマンティック分析は、最終的に、その属性の値を説明する注釈付きASTを生成します。
2.4. 中間コード生成
コンパイルプロセス中に、コンパイラは1つ以上の中間コード形式を生成できます。
「ソースプログラムの構文とセマンティック分析の後、多くのコンパイラは、明示的な低レベルまたはマシンのような中間表現を生成します。これは、抽象マシンのプログラムと考えることができます。 この中間表現には、2つの重要な特性が必要です。つまり、作成が容易である必要があり、ターゲットマシンへの変換が容易である必要があります。」 –
コンパイラ。 原則、テクニック、およびツール。 第2版。 アルフレッドV。 あほ。 コロンビア大学。 モニカS。 ラム。 スタンフォード大学。 ラビ・セシィ。 アバイア。
- 高レベル–ソース言語に似ています。 この形式では、ソースコードのパフォーマンスを簡単に向上させることができます。 ただし、ターゲットマシンのパフォーマンスを向上させるためにはあまり好ましくありません。
- 低レベル–マシンのマシンコードに近い。 マシン関連の最適化を行う場合に推奨されます。
2.5. 最適化
最適化フェーズでは、コンパイラはさまざまな方法を使用してコードの効率を高めます。 確かに、最適化プロセスは3つの重要なルールに従う必要があります。
- 結果のコードは、プログラムの元の意味を変更できません。
- 最適化は、より少ないリソースの消費とソフトウェアの操作の高速化に焦点を当てる必要があります。
- 最適化プロセスは、コンパイルの全体的な時間に大きな影響を与えるべきではありません。
最適化手法の例をいくつか見てみましょう。
- 関数のインライン化–関数呼び出しをその本体に置き換えます。
- デッドコードの除去–コンパイラは、実行されなかったコードを削除します。実行された場合、返された結果は使用されません。
- ループ融合–同じ反復条件を持つ隣接するループからの操作を1つのループで実行します。
- 命令の組み合わせ–同様の操作を実現する命令が1つに結合されます。 たとえば、 x = x + 10; x = x – 7; はx= x +3;に置き換えることができます
2.6. コード生成
最後に、コンパイラは最適化された中間コードをターゲットマシン専用のマシンコードに変換します。 最終的なコードは、ソースコードと同じ意味を持ち、メモリとCPUリソースの使用量の点で効率的である必要があります。 さらに、コード生成プロセスも効率的でなければなりません。
2.7. 実例
以下のフローチャートでは、単純なステートメントのコンパイルプロセスの例を見ることができます。
3. コンパイラ対。 通訳者
すでに知っているように、コンパイラは高レベルのソースコードを低レベルのコードに変換します。 次に、ターゲットマシンは低レベルのコードを実行します。 一方、インタプリタはソースコードを直接分析して実行します。インタプリタは通常、次のいずれかの手法を使用します。
- ソースコードを分析(解析)して直接実行します。
- 高レベルのソースコードを中間コードに変換し、すぐに実行します。
- コンパイラによって生成された、保存されたプリコンパイル済みコードを明示的に実行します。 この場合、コンパイラはインタプリタシステムに属しています。
インタプリタとコンパイラの簡単な比較を見てみましょう。
コンパイラ: | 通訳者: |
1. コードを変換しますが、実行しません。 | 1. コードを直接実行します。 |
2. コンパイラを実装するには、ターゲットマシンに関する知識が必要です。 | 2. インタプリタがコードを実行するため、ターゲットマシンに関する知識は必要ありません。 |
3. 各命令は1回だけ翻訳されます。 | 3. 同じ命令を複数回分析することができます。 |
4. コンパイルされたプログラムは実行が高速です。 | 4. 解釈されたプログラムは実行に時間がかかりますが、コンパイルして実行するよりも解釈にかかる時間が短くなります。 |
5. 中間コード生成により、より多くのメモリを消費します。 | 5. 通常、入力コードを直接実行するため、消費するメモリが少なくなります。 |
6. コンパイルされた言語の例:Java、C ++、Swift、C#。 | 6. 解釈された言語の例:Ruby、Lisp、PHP、PowerShell。 |
4. コンパイラの例
4.1. Javac
Javaでは、ソースコードは最初にjavacコンパイラによってバイトコードにコンパイルされます。 次に、Java仮想マシン(JVM)がバイトコードを解釈して実行します。 したがって、javacは、インタプリタシステムに属するコンパイラの優れた例です。 このようなシステムにより、Javaはポータブルでマルチプラットフォームになります。
さらに、KotlinやScalaのようにバイトコードにコンパイルされる他の言語もありますが、これらは独自のコンパイラーを使用しています。 したがって、JVMは、元々さまざまなテクノロジを使用して記述されたコードを実行できます。
4.2. 単核症
Mono は、.NETプラットフォーム専用のソフトウェアを実行するためのC#プログラミング言語コンパイラを含むツールセットです。 これは、.NETアプリケーションをさまざまなプラットフォームで実行できるようにするために作成されました。 さらに、重要な目標は、Linuxで作業する開発者に、.NETプラットフォームで作業するためのより良い環境とツールを提供することでした。
コンパイラは、C#ソースコードを中間バイトコードに変換します。 その後、仮想マシンが実行します。 C#コンパイラと仮想マシンの両方がMonoツールセットに属しています。
4.3. GNUコンパイラコレクション
GNUコンパイラコレクション(GCC)は、 GNUProjectに属するオープンソースコンパイラのセットです。 これらのコンパイラは、さまざまなハードウェアおよびソフトウェアプラットフォームで実行できます。 したがって、さまざまなアーキテクチャやオペレーティングシステム用のマシンコードを生成できます。
コンパイル中、GCCは引数の処理、特定のプログラミング言語用のコンパイラーの呼び出し、アセンブラープログラムの実行、そして最終的にはリンカーを実行して実行可能バイナリーを生成する責任があります。
GCCは、いくつかのプログラミング言語用のコンパイラで構成されています。
- C(gcc)
- C ++(g ++)
- Objective-C(gobjc)
- Fortran(g77およびGFortran)
- Java(gcj)
- エイダ(gnat)
5. 結論
この記事では、コンパイラの役割について説明しました。 さらに、コンパイルプロセスのすべてのフェーズを実行しました。 次に、コンパイラとインタプリタの違いについて説明しました。 最後に、実際のコンパイラの例について説明しました。