低级语言及其依赖

Low level languages and their dependencies

我正在尝试理解低级语言依赖于机器的确切含义。

让我们以 C 为例,如果它依赖于机器,是否意味着如果它是在一台计算机上编译的,它可能无法 运行 在另一台计算机上?

依赖于机器意味着该程序只能在特定硬件上运行。我能举出的最好的例子是 Assembly。让我们考虑一下您的示例, C 是与机器无关的语言。您可以在不同的系统上执行相同的程序。

最终处理器执行的机器代码基本上是二进制数的集合。处理器解码每个二进制数以弄清楚它应该做什么。一个二进制数可能意味着“将寄存器 X 添加到寄存器 Y,并将结果存储在寄存器 Z”。另一个二进制数可能表示“将寄存器 X 的内容存储到寄存器 Y 所保存的内存地址中”。等等...

这些解码规则(即二进制数转换成运算)的完整描述表示处理器指令集(又名 ISA)。

低级语言是一种您可以编写的代码与特定处理器指令集非常接近的语言。组装是一个明显的例子。由于不同的处理器可能有不同的指令集,很明显,为一个处理器 ISA 编写的汇编程序不能在具有不同 ISA 的处理器上使用。

Let's take for example C, well if it is machine-dependent does it mean that if it was compiled on one computer it might not be able to run on another?

正确。为一个处理器(系列)编译的程序不能在另一个具有(完全)不同 ISA 的处理器上 运行。程序需要重新编译。

另请注意,目标 OS 也发挥了作用。如果您使用相同的处理器但使用不同的 OS,您还需要重新编译。

至少有 3 种不同的语言。

  1. 一种与目标系统 ISA 非常接近的语言,以至于源代码只能在该特定目标上使用。示例:程序集

  2. 一种允许您使用目标特定编译编写可用于许多不同目标的代码的语言。示例:C

  3. 一种允许您编写可用于许多不同目标的代码而无需特定目标编译的语言。这些仍然需要安装某种特定于目标的 运行 时间环境。示例:Java.

高级语言是可移植的,这意味着每个体系结构都可以 运行 高级程序,但是与低级程序(例如用 Assembly 甚至机器代码编写的)相比,它们是效率较低,消耗更多内存。

低级程序被称为“更接近硬件”,因此它们针对特定类型的硬件进行了优化architecture/processor,是更快的程序,但相对依赖于机器或不太便携. 因此,为一种类型的处理器编译的程序对其他类型无效;需要重新编译。

之前

当第一个处理器问世时,还没有任何编程语言,您有一个非常长且非常复杂的文档,其中包含“操作码”列表:您必须为给定操作放入内存的代码在你的处理器中执行。要创建一个程序,您必须在内存中放入一长串数字,并希望一切都像记录的那样工作。

后来出现了汇编语言。重点并不是让算法更容易实现,也不是让任何对你正在使用的特定处理器模型没有任何经验的人都可以阅读程序,它的创建是为了让你免于花费数天和数天的时间在文档。出于这个原因,没有“一种汇编语言”,而是成千上万种,每个指令集一个(当时,基本上意味着每个 CPU 模型一个)

此时,所有语言都依赖于平台。如果您决定切换 CPUs,则必须重写大部分(如果不是全部)代码。认识到这是一个问题,有人创建了第一个独立于平台的语言(根据 this SE question 它是 1954 年的 FORTRAN),可以在任何 CPU 架构上编译为 运行只要有人为它做了一个编译器。

快进一点,C 被发明了。 C 是一种独立于平台的编程语言,任何 C 程序(只要它符合标准)都可以在任何 CPU 上编译为 运行(只要这个 CPU 有一个 C 编译器)。编译 C 程序后,生成的文件是依赖于平台的二进制文件,并且只能 运行 在其编译的体系结构上。

C 依赖于平台

但是有一个问题:处理器不仅仅是一个操作码列表。大多数处理器都有硬件控制设备,如看门狗或定时器,它们在一种架构与另一种架构之间可能完全不同,甚至与其他设备通信的方式也可能完全改变。因此,如果您想在 CPU 上实际 运行 一个程序,您必须包含使其依赖于平台的内容。

一个真实的例子是 Linux 内核。大部分内核是用 C 语言编写的,但仍有大约 1% 的内核是用不同类型的汇编语言编写的。此程序集需要执行诸如初始化 CPU 或使用计时器之类的操作。使用此 hack 意味着 Linux 可以 运行 在您的桌面 x86_64 CPU、您的 ARM Android phone 或 RISCV SoC 上添加任何新架构并不只是“用您的体系结构的编译器编译它”那么简单。

所以...我刚才是否说过 运行 在实际处理器上独立于平台的唯一方法是使用平台相关代码?是的,对于大多数架构,您必须这样做。

或者是吗?

但是有一个问题!仅当您想要 运行 在裸机上编码(意思是:没有 OS 时)才是正确的。使用 OS 的一大优点是一切都是如此抽象:您不需要知道内核如何初始化 CPU,也不需要知道它如何获得时钟,您只需要知道如何访问那些抽象的资源。

但是访问资源的方式依赖于OS,这不是回到原点吗?如果不是标准库,我们可以!该库用于以定义的方式访问 printf 等函数。无论您是在 PowerPC 上还是在 ARM Windows 上使用 Linux 运行ning 都没有关系,printf 将始终以相同的方式在标准输出上打印内容。

如果您仅使用标准库编写标准 C(并打算让您的程序在 OS 中 运行)C 完全独立于平台!

编辑:如以下评论所述,即使这样还不够。它实际上与特定的 CPU 没有任何关系,但是 system 函数或某些类型的大小等某些内容被记录为实现定义的。要使 C 真正独立于平台,您需要确保仅使用 STL 的定义明确的函数并学习一些最佳实践(永远不要依赖 sizeof(int)==4)。

思考“什么是程序”可能有助于您理解您的问题。程序是文本的集合(您输入的或以其他方式制作的)还是您 运行 的东西?两者都有吗?

对于像 C 这样的 'low-level' 语言,我会说文本是程序源代码,并且由编译器将其转换为程序(也称为可执行文件)。程序是你可以 运行 的东西。您需要一个系统的 C 编译器才能将程序源代码转换为该系统的程序。一旦构建,该程序只能在接近其编译目标的系统上运行 运行。然而,还有一个更有趣但更难的问题:您能否至少保持程序源不变,以便您需要做的就是重新编译?答案是 'sort-of No' 我有点想。例如,您不能在纯 C 中读取 shift 键的状态。当然操作系统提供了这样的设施,你可以与 C 中的那些接口,但是这样的代码依赖于 OS。可能有库(例如 curses 库)为许多 OS 提供这样的设施并且可以帮助减少依赖性,但是没有库可以声称可移植地涵盖所有 OS.

对于像 python 这样的 'higher-level' 语言,我会说文本既是程序又是程序源。此类语言没有单独的编译阶段,但您确实需要系统上的解释器才能 运行 您的 python 程序在该系统上。然而,用户可能不清楚这种情况的发生,因为您似乎可以 运行 您的 python 'program' 只需像您一样命名它 运行 您的 C程式。但这很可能归结为 shell(OS 中处理命令的部分)了解 python 程序并为您调用解释器。看起来你可以在任何地方 运行 你的 python 程序,但实际上你可以做的是将程序传递给任何 python 解释器。

在编程的动物园里,不仅野兽数量多、种类繁多,而且新的野兽不断出现,旧的野兽不断蜕变。 'program'、'script' 甚至 'executable' 等术语经常被随意使用。