コンパイルは、ある言語から別の言語への単純な翻訳です。A
同じものを言語と言語で表現できれば、言語で表現されたものを言語で同じものにB
コンパイルできます。A
B
ある言語で意図を表現したら、それは解釈されることによって実行されます。C やその他のコンパイル済み言語を実行している場合でも、ステートメントは次のようになります。
- C からの翻訳 -> アセンブリ言語
- アセンブリから翻訳 -> マシンコード
- 機械によって解釈されます。
コンピューターは、実際には非常に基本的な言語のインタープリターです。それは非常に基本的で扱いが難しいため、より扱いやすく、機械語 (C など) の同等のステートメントに簡単に変換できる他の言語が考え出されました。次に、JIT コンパイラーのように「オンザフライ」で変換を実行するか、高級言語 (LISP や Python など) でステートメントを直接実行する独自のインタープリターを作成することで、コンパイル フェーズを乗っ取ることができます。
ただし、インタープリターはコードを直接実行するためのショートカットにすぎないことに注意してください。コードを実行する代わりに、インタープリターが実行する呼び出しを出力した場合、コードを実行するには... コンパイラーが必要です。もちろん、それは非常にばかげたコンパイラであり、持っている情報のほとんどを利用できません。
実際のコンパイラは、コードを生成する前に、プログラム全体からできるだけ多くの情報を収集しようとします。たとえば、次のコード:
const bool dowork = false;
int main() {
if (dowork) {
//... lots of code go there ...
}
return 0;
}
if
理論的には、ブランチ内のすべてのコードを生成します。しかし、賢いコンパイラは、プログラム内のすべてを知っていて、それdowork
が常にfalse
.
それに加えて、一部の言語には型があり、関数呼び出しをディスパッチするのに役立ち、コンパイル時にいくつかのことを保証し、機械語への変換に役立ちます。C などの一部の言語では、プログラマーが変数の型を宣言する必要があります。LISP や Python などの他のものは、変数が設定されたときに変数の型を推測するだけで、別の型が必要な場合に特定の型の値を使用しようとすると、実行時にパニックになります (たとえば(car 2)
、ほとんどの Lisp インタープリターで記述した場合、エラーが発生します)。ペアが必要であることを示すエラー)。型を使用してコンパイル時にメモリを割り当てることができます (たとえば、C コンパイラは、10 * sizeof(int)
を割り当てる必要がある場合、正確にバイトのメモリを割り当てますint[10]
)。ただし、これは正確には必要ありません。. 実際、ほとんどの C プログラムはポインタを使用して配列を格納しますが、これは基本的に動的です。ポインターを処理するとき、コンパイラーは実行時に必要なチェックや再割り当てなどを実行するコードを生成/リンクします。Python または Lisp インタープリターはコンパイルされたプログラムですが、それでも動的な値に作用できます。実際、アセンブリ言語自体は実際には型付けされていません。コンピューターが「認識する」のはビットのストリームとビットに対する操作だけであるため、コンピューターは任意のオブジェクトに対して任意の操作を実行できます。高水準言語は、任意の型と制限を導入して、物事を読みやすくし、完全にクレイジーなことをしないようにします。しかし、これはあなたを助けるためのものであり、絶対的な要件ではありません.
哲学的な暴言が終わったので、あなたの例を見てみましょう:
(setq x 60)
(setq y 40)
(+ x y)
それを有効な C プログラムにコンパイルしてみましょう。それが完了すると、C コンパイラが豊富にあるので、LISP -> C -> 機械語、またはその他のほとんどすべてを翻訳できます。コンパイルは翻訳にすぎないことに注意してください (最適化もクールですが、オプションです)。
(setq
これは値を割り当てます。しかし、何が何に割り当てられているかはわかりません。続けましょう
(setq x 60)
わかりました、x に 60 を割り当てます。60 は整数リテラルなので、その C 型はint
. が別の型であると仮定する理由がないためx
、これは C:
int x = 60;
同様に(setq y 40)
:
int y = 40;
これで、次のようになりました。
(+ x y)
+
実装に応じて、いくつかのタイプの引数を取ることができる関数ですが、x
とy
が整数であることはわかっています。私たちのコンパイラは、次の同等の C ステートメントが存在することを認識しています。
x + y;
だから私たちはそれを翻訳するだけです。最終的な C プログラム:
int x = 60;
int y = 40;
x + y;
これは完全に有効な C プログラムです。これよりもさらにトリッキーになる可能性があります。たとえば、x
とy
が非常に大きい場合、ほとんどの LISP はそれらをオーバーフローさせませんが、C はオーバーフローさせません。そのため、コンパイラをコーディングして、int の配列 (または関連するもの) として独自の整数型を持たせることができます。これらの型に対して一般的な操作 ( など) を定義できる場合+
、新しいコンパイラは以前のコードを代わりに次のように変換する可能性があります。
int* x = newbigint("60");
int* y = newbigint("40");
addbigints(x, y);
関数newbigint
を使用addbigints
して、他の場所で定義するか、コンパイラによって生成されます。まだ有効な C であるため、コンパイルされます。実際、独自のインタープリターはおそらく低レベル言語で実装されており、独自の実装で LISP オブジェクトの表現を既に持っているため、これらを直接使用できます。
ちなみに、それはまさにCythonコンパイラがPythonコードに対して行うことです:)
Cython で型を静的に定義して、速度と最適化を向上させることができますが、必須ではありません。Cython は、Python コードを直接 C に変換し、次にマシン コードに変換できます。
より明確になることを願っています!覚えて:
- すべてのコードが解釈され、最終的に
- コンパイラは、コードを解釈しやすい/速いものに変換します。多くの場合、途中で最適化を実行しますが、これは定義の一部ではありません