一、技术差异

一、技术差异

我写了一个bash脚本,我没有先编译就执行了它。它工作得很好。它可以在有权限或没有权限的情况下工作,但是当涉及到C程序时,我们需要编译源代码。为什么?

答案1

这意味着 shell 脚本不是编译的,而是解释的:shell 一次解释一个命令,并每次都弄清楚如何执行每个命令。这对于 shell 脚本来说是有意义的,因为无论如何它们大部分时间都花在运行其他程序上。

另一方面,C 程序通常是经过编译的:在运行之前,编译器将它们一劳永逸地完整地转换为机器代码。过去曾有过C解释器(例如海辉软件Atari ST 上的 C 解释器),但它们非常不寻常。如今,C 编译器速度非常快;TCC速度如此之快,您可以使用它来创建“C 脚本”,使用#!/usr/bin/tcc -runshebang,这样您创建以与 shell 脚本相同的方式运行的 C 程序(从用户的角度来看)。

有些语言通常同时具有解释器和编译器:BASIC 就是我想到的一个例子。

您还可以找到所谓的 shell 脚本编译器,但我见过的只是混淆包装器:它们仍然使用 shell 来实际解释脚本。作为追踪器指出虽然适当的 shell 脚本编译器肯定是可能的,但不是很有趣。

另一种思考方式是认为 shell 的脚本解释能力是其命令行处理能力的扩展,这自然会导致解释方法。另一方面,C 被设计为生成独立的二进制文件;这导致了一种编译方法。通常编译的语言也往往会产生解释器,或者至少是命令行解析器(称为 REPL,读取-求值-打印循环; shell 本身就是一个 REPL)。

答案2

这一切都归结为人类读/写的程序如何转换为计算机可以理解的机器指令之间的技术差异 - 并且每种方法的不同优点和缺点是编写某些语言需要编译器的原因,有些是为了解释而编写的。

一、技术差异

(注意:为了解决这个问题,我在这里进行了很多简化。为了更深入地理解,我的答案底部的技术注释详细说明/完善了这里的一些简化,并且对此答案的评论有还有一些有用的澄清和讨论..)

编程语言基本上有两大类:

  1. 另一个程序(“编译器”)读取您的程序,确定您的代码要执行的步骤,然后编写一个新程序在执行这些步骤的机器代码(计算机本身理解的“语言”)中。
  2. 另一个程序(“解释器”)读取您的程序,确定您的代码要执行的步骤,然后自己执行这些步骤。没有创建新程序。

C 属于第一类(C编译器翻译 C语言进入你的计算机机器码:机器代码保存到文件中,然后当您运行该机器代码时,它会执行您想要的操作)。

bash 属于第二类(bash口译员读取 bash语言和狂欢口译员做你想要的:所以本身没有“编译器模块”,解释器负责解释和执行,而编译器负责读取和翻译)。

您可能已经注意到这意味着什么:

使用 C,您执行“解释”步骤一次,那么每当你需要运行程序时,你只需告诉你的计算机执行机器代码——你的计算机就可以直接运行它,而不需要做任何额外的“思考”。

使用bash,你必须执行“解释”步骤每次你运行该程序 - 你的计算机正在运行 bash 解释器,并且 bash 解释器每次都会进行额外的“思考”来弄清楚它需要为每个命令执行什么操作。

因此,C 程序需要更多的 CPU、内存和时间来准备(编译步骤),但运行的时间和工作却更少。 bash 程序需要更少的 CPU、内存和时间来准备,但运行时需要更多的时间和工作。大多数时候您可能不会注意到这些差异,因为现在的计算机速度非常快,但它确实会产生影响,并且当您需要运行大型或复杂的程序或许多小程序时,这种差异会增加。

此外,由于 C 程序会转换为计算机的机器代码(“本机语言”),因此您无法将程序复制到具有不同机器代码的另一台计算机上(例如,将 Intel 64 位复制到 Intel 32 位)位,或从 Intel 到 ARM 或 MIPS 或其他)。您必须花时间为其他机器语言编译它再次。但是 bash 程序可以转移到另一台安装了 bash 解释器的计算机上,并且它会运行得很好。

现在为什么你的问题的一部分

几十年前,C 语言的开发者就在硬件上编写了操作系统和其他程序,这在很大程度上受到现代标准的限制。由于各种原因,将程序转换为计算机的机器代码是当时他们实现这一目标的最佳途径。另外,他们所做的工作是他们编写的代码运行很重要有效率的

Bourne shell 和 bash 的制造者想要的恰恰相反:他们想要编写可以立即执行的程序/命令 - 在命令行上,在终端中,您只需编写一行,一个命令,然后就可以了执行。他们希望您编写的脚本能够在安装了 shell 解释器/程序的任何地方工作。

结论

简而言之,您不需要 bash 编译器,但需要 C 编译器,因为这些语言以不同的方式转换为实际的计算机操作,并且选择了不同的执行方式,因为这些语言有不同的目标。

其他技术/高级细节/注释

  1. 你实际上可以创建一个 C口译员,或者一场狂欢编译器。没有什么可以阻止这种可能性:只是这些语言是为了不同的目的而创建的。用另一种语言重写程序通常比为复杂的编程语言编写良好的解释器或编译器更容易。特别是当这些语言有它们擅长的特定事物,并且首先是按照某种工作方式设计的。 C 被设计为可编译的,因此它缺少许多您在交互式 shell 中需要的方便的简写,但它非常适合表达非常具体的、低级的数据/内存操作以及与操作系统的交互,当您想要编写高效编译的代码时,您经常会发现自己正在执行这些任务。同时,bash 非常擅长执行其他程序、重定向文件/文件描述符以及处理文本字符串,并且它对这些程序有方便的速记,因为这些是您经常想要在交互式 shell 中执行的任务。

  2. 更高级的细节:实际上有一些编程语言是两种类型的混合(它们“大部分”翻译源代码,这样它们就可以一次完成大部分解释/“思考”,并且只做一点点稍后的解释/“思考”)。 Java、Python 和许多其他现代语言实际上都是这样的混合体:它们试图为您提供解释型语言的一些可移植性和/或快速开发的优势,以及编译型语言的一些速度。有很多可能的方法来组合这些方法,并且不同的语言有不同的做法。如果您想深入研究这个主题,您可以阅读编译为“字节码”的编程语言(这有点像编译为您自己编写的“机器语言”,然后您可以快速有效地解释它)和“JIT” “(即时编译,您可以在解释或运行程序时编译甚至重新编译程序)。

  3. 您询问了执行位:实际上,可执行位只是告诉操作系统该文件是允许被处决。我怀疑 bash 脚本为你工作的唯一原因是没有执行权限实际上是因为您是从 bash shell 内部运行它们。通常,当要求操作系统执行未设置执行位的文件时,操作系统只会返回错误。但是像 bash 这样的一些 shell 会看到该错误,并通过基本上模拟操作系统通常采取的步骤(查找文件开头的“#!”行,然后尝试)自行运行该文件执行该程序来解释文件,默认为本身或/bin/sh如果没有“#!”行)。

  4. 有时您的系统上已经安装了编译器,有时 IDE 带有自己的编译器和/或为您运行编译。这可能会使编译语言使用起来“感觉”像非编译语言,但技术差异仍然存在。

  5. “编译”语言不一定会编译成机器代码,整个编译本身就是一个主题。基本上,这个术语的使用很广泛:它实际上可以指代一些东西。在某种特定意义上,“编译器”只是从一种语言(通常是更易于人类使用的“高级”语言)到另一种语言(通常是更易于计算机使用的“低级”语言)的翻译器 -有时,但实际上并不经常,这是机器代码)。另外,有时当人们说“编译器”时,他们实际上是在谈论一起工作的多个程序(对于典型的 C 编译器,它实际上是四个程序:“预处理器”、编译器本身、“汇编器”和“链接器”)。

答案3

考虑以下程序:

2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes

顺便说一句bash,你在商店里闲逛寻找火星棒,最后找到它们,然后四处闲逛寻找牛奶等。这是有效的,因为你正在运行一个名为“经验丰富的购物者”的复杂程序,当你看到面包时它可以识别面包以及购物的所有其他复杂性。bash是一个相当复杂的程序。

或者,您可以将购物清单交给购物编辑器。编译器思考一会儿并给你一个新的列表。这份清单是长的,但包含更简单的指令:

... lots of instructions on how to get to the store, get a shopping cart etc.
move west one aisle.
move north two sections.
move hand to shelf three.
grab object.
move hand to shopping cart.
release object.
... and so on and so forth.

正如您所看到的,编译器确切地知道商店中所有东西的位置,因此不需要整个“寻找东西”阶段。

这本身就是一个程序,不需要“有经验的购物者”来执行。它所需要的只是一个拥有“基本人类操作系统”的人类。

回到计算机程序:bash是“有经验的购物者”,可以获取脚本并直接执行而不编译任何内容。 AC 编译器生成一个独立的程序,不再需要任何帮助即可运行。

解释器和编译器都有各自的优点和缺点。

答案4

想象一下英语不是您的母语(如果英语不是您的母语,这对您来说可能很容易)。

您可以通过 3 种方式阅读本文:

  1. (口译)阅读时,每次看到的单词都要翻译
  2. (优化-解释)找到常用短语(例如“您的母语”),翻译并写下来。然后,翻译每个单词 - 除了您已经翻译的短语
  3. (已编译)请别人翻译整个答案

计算机有某种“本机语言”——处理器可以理解的指令和操作系统(例如Windows、Linux、OSX 等)可以理解的指令的组合。这种语言不是人类可读的。

脚本语言(例如 Bash)通常属于第一类和第二类。它们一次读取一行,翻译该行并运行它,然后转到下一行。在 Mac 和 Linux 上,默认为不同的语言安装了相当多不同的解释器,例如 Bash、Python 和 Perl。在 Windows 上,您必须自己安装这些。

许多脚本语言都会进行一些预处理 - 尝试通过编译经常运行的代码块来加快执行速度,否则会减慢应用程序的速度。您可能听说过的一些术语包括提前 (AOT) 或即时 (JIT) 编译。

最后,编译语言(例如 C)会在运行整个程序之前对其进行翻译。这样做的优点是翻译可以在不同的机器上执行,因此当您将程序交给用户时,虽然可能仍然存在错误,但几种类型的错误已经可以清除。就像如果你把这个给你的翻译,我提到garboola mizene resplunks,这对你来说可能看起来像有效的英语,但翻译可以告诉你我在胡说八道。当您运行已编译的程序时,它不需要解释器 - 它已经是计算机的本机语言

然而,编译语言有一个缺点:我提到计算机有一种本机语言,由硬件和操作系统的功能组成 - 好吧,如果您在 Windows 上编译程序,您不会期望编译的程序可以在 Windows 上运行一台Mac。有些语言通过编译为一种半途语言来解决这个问题 - 有点像洋泾浜英语 - 这样,您可以获得编译语言的好处,以及速度的小幅提升,但这确实意味着您需要捆绑带有您的代码的解释器(或使用已安装的解释器)。

最后,您的 IDE 可能正在为您编译文件,并且可以在运行代码之前告诉您错误。有时,这种错误检查可能比编译器所做的更深入。编译器通常只会检查所需的数量,以便生成合理的本机代码。 IDE 通常会运行一些额外的检查,并可以告诉您,例如,您是否已定义一个变量两次,或者是否导入了未使用过的内容。

相关内容