The Abstract Computer: なぜ必要なのか?
どんなプログラミング言語を学ぶ前に、まず頭の中に持っておくべき重要な考え方は、非常に限定的な場合を除いて、開発者は常にコンピュータの抽象をコード化し、特定のコンピュータハードウェアをターゲットにすることはありません。Pythonのような高レベルの言語でコードを書く場合、抽象的なコンピュータをターゲットにします。Cのような低レベルの言語でコードを書く場合でも、抽象的なコンピュータをターゲットにします。直接機械語を書く場合でも(機械語は00111001010100111のようなバイナリのシーケンスであり、人間にはほとんど読めませんが、コンピュータハードウェア上で直接実行できます)、抽象的なコンピュータをターゲットにします。
なぜそうなのでしょうか? なぜ追加の抽象のレイヤーが必要なのでしょうか? ハードウェアに直接コードを書く方がシンプルではないでしょうか? 抽象的なコンピュータに関しては、以下で2つの理論的根拠を説明します。
標準化の必要性
以前のモジュールで提供された ISA(命令セットアーキテクチャ)の例を反映して、異なるコンピュータ間で共有される抽象(標準)を持つことで、同じソフトウェアが異なるハードウェア上で実行され、同じハードウェアが異なるアプリケーションを実行できます。つまり、抽象の重要な点は、異なるコンピュータとソフトウェアアプリケーション間で共有されるという点です。
このような共有の抽象がない場合、ソフトウェアアプリケーションは特定のコンピュータハードウェアを直接ターゲットします。つまり、1つのソフトウェアアプリケーションが1つのハードウェアタイプ、例えばIntel Core i7 12700Hのような特定のCPUモデルでしか実行できません。異なるコンピュータをサポートするためには、ソフトウェアをそれぞれのコンピュータのためにコード化する必要があります。逆に、コンピュータはそれを特定するソフトウェアアプリケーションのみを実行できます。共有の抽象がない場合は、ソフトウェア開発が非常に困難で非効率であり、コンピュータの機能も非常に制限されています。
しかし、抽象のターゲットとしてコンピュータを指定すると、非常に簡単になります。アプリケーションがターゲットとする抽象を共有しているコンピュータで実行できます。同じ抽象を共有するすべてのコンピュータは、その抽象をターゲットとする任意のソフトウェアアプリケーションを実行できます。以下のように示されています。
%%{init: { "flowchart": { "curve": "linear" } } }%%
graph TD
Software-1 --> ISA
Software-2 --> ISA
Software-3 --> ISA
ISA --> Computer-1
ISA --> Computer-2
ISA --> Computer-3
現時点では、最も低レベルの抽象コンピュータは、命令セットアーキテクチャ(ISA)として考えることができます。ISAは、コンピュータがサポートする操作(加算や乗算などの数学操作など)を定義する標準です。現在、数百、数千のCPUモデルが存在しますが、わずかなISAファミリーしかありません。最も普及しているISAファミリーはx86であり、ほとんどのノートパソコンやPC(新しい世代のMacを除く)でサポートされています。この場合、共有の抽象(x86など)をターゲットにすることは、特定のハードウェアに対して個別にコードを書くよりも何千倍も生産性が高いです。
Abstraction Makes Coding Easier
命令セットアーキテクチャ(ISA)は標準化の必要性を満たします。しかし、現代では、実際にアセンブリ言語(つまり、ISAによって定義された操作を直接使用するコード)や機械語(つまり、ISA標準で定義されたバイナリコードで、ハードウェア上で直接実行できるコード)を書く開発者は非常に少ないです。
その理由は、ISAがハードウェアと直接インターフェースする最も低い抽象のレベルです。その結果、ほとんど人間が読み取れず、大規模で複雑なアプリケーションをコーディングすることは非常に困難です。以下は、アセンブリ言語(x86)の例です(現在は理解する必要はありませんが)。
push %rbp
mov %rsp,%rbp
lea 0xe4c(%rip),%rax
mov %rax,%rsi
lea 0x2e7e(%rip),%rax
mov %rax,%rdi
call 1090 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
mov 0x2dff(%rip),%rdx
mov %rdx,%rsi
mov %rax,%rdi
call 10a0 <_ZNSolsEPFRSoS_E@plt>
mov $0x0,%eax
pop %rbp
ret
(簡潔さのために完全な逆アセンブルは省略されており、このコードの最も重要な部分のみを示しています)
上記のコードの機能は、"Hello World"をコンピュータの画面に表示することですが、どのように表示されるかを理解する必要はありません。重要な点は、ISAに直接コーディングすることは人間には向いていないということです。
そのため、開発者は、複雑なソフトウェアシステムを簡単に設計、構築、理解するためのより高レベルの抽象が必要です。おおよそ言えば、それらの抽象はまさに高レベルのプログラミング言語です。 例えば、上記のアセンブリコードは、人気のある低レベルのプログラミング言語であるC++では以下のように見えます(今は何をしているかを理解する必要はありません)。
同じことを行うPythonのコードは、以下のようになります(高レベルのプログラミング言語です)。
見てわかるように、より高レベルの抽象(プログラミング言語)をターゲットにしてコーディングすると、コーディングがはるかに簡単になります。ただし、プログラミング言語は互いに独立せず、単体のピースではありません。プログラミング言語間で共有される多くの抽象があります。たとえば、ほとんどのプログラミング言語には変数と構造体の概念があります。C/C++やRustのような低レベルのプログラミング言語には、スタックとヒープという抽象があります。C++、Java、Dartのようなオブジェクト指向やマルチパラダイムの言語には、クラス、オブジェクト、インターフェースの概念が通常あります。ただし、これらの用語が何を意味するのかを理解する必要はありません。後のセクションとモジュールで説明します。
おめでとうございます!抽象的なコンピュータとその背後にある理論的根拠について学びました。次は、すべての命令セットアーキテクチャ(ISA)で共有される最も低レベルの抽象を見ていきます。