跳转至

抽象计算机:我们为什么需要它?

在学习任何编程语言之前,我们首先需要了解的一点是, 除了极少数特定情况,开发者总是为了计算机的抽象而编码, 而不是针对具体的计算机硬件。 如果你使用高级语言如Python进行编码,你是在针对抽象计算机编码; 如果你使用低级语言如C进行编码,你仍然在针对抽象计算机编码; 甚至,如果你直接编写机器码 (机器码是二进制序列,如00111001010100111,虽然对人类来说几乎无法读取,但可以直接在计算机硬件上运行), 你仍然是在针对抽象计算机编码。

为什么会这样呢? 为什么我们需要一个额外的抽象层? 直接为硬件编码不是更简单吗? 以下是使用抽象计算机的两个原因。

标准化需求

正如在前一模块中提到的ISA示例(指令集架构)所反映的那样, 在不同计算机上有一个共享的抽象(标准)使得同一软件可以运行在不同的硬件上, 同样的硬件也能运行不同的应用程序。 也就是说,抽象的重要性在于它可以在不同的计算机和软件应用之间共享。

如果没有这样的共享抽象, 软件应用将直接针对具体的计算机硬件, 这意味着一个软件应用只能运行在一种类型的硬件上, 例如,一种特定的CPU型号,如Intel Core i7 12700H。 如果我们想要支持不同的计算机,我们将不得不为每个计算机编写软件。 反过来,一台计算机只能运行特定于它的软件应用。 可以看到,在没有共享抽象的情况下, 软件开发是极其困难和低效的, 计算机的功能也非常有限。

然而,通过针对抽象计算机,情况变得简单得多: 只要计算机共享应用程序所针对的抽象, 该应用程序就可以在该计算机上运行; 所有共享相同抽象的计算机都能够运行针对该抽象的任何软件应用程序, 如下所示:

%%{init: { "flowchart": { "curve": "linear" } } }%%
graph TD

软件应用-1 --> ISA
软件应用-2 --> ISA
软件应用-3 --> ISA

ISA --> 计算机-1
ISA --> 计算机-2
ISA --> 计算机-3

目前,可以将最低级别的抽象计算机视为指令集架构(ISA), 它是定义计算机必须支持的操作的标准,例如加法和乘法等数学操作。 目前,有数百甚至数千种CPU型号, 但只有几个ISA家族。 最普遍的ISA家族是x86,几乎支持每台笔记本电脑和个人电脑 (除了较新一代的Mac)。 在这种情况下,针对共享抽象(如x86)进行编码可能比为每个特定硬件编码高效上千倍。

抽象使编码更容易

指令集架构(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++中的表示如下, C++是一种流行的低级编程语言 (同样,现在不需要理解它正在做什么):

#include <iostream>

int main() {
    std::cout << "Hello World" << std::endl;
    return 0;
}

而在Python中,执行相同操作的代码如下,Python是一种高级编程语言:

print("Hello World")

可以看到,针对高级抽象(编程语言)编码使得编码变得更加容易。 然而,编程语言并不是彼此独立的, 它们也不是一整块的东西; 有许多抽象在多个编程语言之间共享。 例如,几乎所有编程语言都有变量和结构体的概念; 低级编程语言如C/C++和Rust通常有栈和堆的抽象; 面向对象或多范式语言如C++、Java和Dart通常具有类、对象和接口的概念。 现在不需要理解这些词的含义,因为我们将在后面的章节和模块中进行说明。

恭喜!你已经了解了抽象计算机及其背后的原理。 接下来,我们将看一下计算机的最低级别的抽象, 这个抽象在所有指令集架构(ISA)之间共享。