为什么 C 编程需要编译器而 shell 脚本不需要?

ARe*_*ddy 9 compiling shell-script

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

Ste*_*itt 24

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

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

一些语言通常同时具有解释器和编译器:BASIC 就是一个浮现在脑海中的例子。

您还可以找到所谓的 shell 脚本编译器,但我见过的那些只是混淆包装器:它们仍然使用 shell 来实际解释脚本。正如mtraceur指出的那样,虽然一个合适的 shell 脚本编译器肯定是可能的,只是不是很有趣。

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

  • 请注意,shell 的“真正”编译器是完全可能的。它通常不值得付出努力/复杂性,因为一个幼稚的变体只会产生一个程序,它通常会调用一堆 `execve`、`open`、`close`、`read`、`write` 和 `pipe` 系统调用,穿插着一些 `getenv`、`setenv` 和内部 hashmap/array 操作(用于非导出变量)等。 - 订购等 (2认同)

mtr*_*eur 8

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

一、技术差异

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

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

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

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

bash 属于第二类(bash解释器读取 bash语言,bash解释器执行您想要的操作:因此本身没有“编译器模块”,解释器负责解释和执行,而编译器负责读取和翻译) .

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

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

使用 bash,您每次运行程序都必须执行“解释”步骤——您的计算机正在运行 bash 解释器,而 bash 解释器每次都会进行额外的“思考”以弄清楚它需要为每个命令做什么.

因此,C 程序需要更多的 CPU、内存和时间来准备(编译步骤),但运行所需的时间和工作更少。bash 程序需要更少的 CPU、内存和时间来准备,但更多的时间和工作来运行。大多数时候您可能不会注意到这些差异,因为如今计算机的速度非常快,但它确实有所作为,当您需要运行大型或复杂的程序或许多小程序时,这种差异就会累积起来。

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

现在是你问题的原因部分

几十年前,C 的制造商在硬件上编写操作系统和其他程序,这受到现代标准的限制。出于各种原因,将程序转换为计算机的机器代码是当时实现该目标的最佳方式。另外,他们所做的工作非常重要,他们编写的代码可以高效运行。

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

结论

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

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

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

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

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

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

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


Sti*_*mer 6

考虑以下程序:

2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes
Run Code Online (Sandbox Code Playgroud)

bash这样,你在店里徘徊寻找火星吧,最后找到他们,然后闲逛寻找牛奶等,这工作,因为你正在运行一个名为“有经验的购物者”复杂的程序,当你看到一个能够识别面包以及购物的所有其他复杂性。bash是一个相当复杂的程序。

或者,您可以将购物清单交给购物编译器。编译器想了一会儿,然后给你一个新的列表。这个列表是LONG,但包含更简单的指令:

... 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.
Run Code Online (Sandbox Code Playgroud)

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

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

回到计算机程序:bash是“有经验的购物者”,可以使用脚本,无需编译任何东西即可完成。AC 编译器生成一个独立的程序,不再需要任何帮助来运行。

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

  • 很好的比喻……也解释了为什么你可以在不同的架构上使用相同的购物清单但不能使用相同的机器代码(原文如此!)——所有的东西都在不同的位置等等。你*可能*需要一个不同的“有经验的购物者” “虽然谁知道不同的超市。 (3认同)

Jul*_*ier 5

可以编译或解释编程/脚本语言。

编译后的可执行文件总是更快,并且在执行之前可以检测到许多错误。

解释型语言通常比编译型语言更易于编写和适应,并且不那么严格,并且不需要编译,这使得它们更容易分发。

  • **总是**是一个危险的词...... (30认同)
  • *优化* 编译后的 CPython 通常比优化的 asm.js(JavaScript 的子集)慢。因此,有一个例子说明它不是更快,因此它并不“总是”更快。但是,它*通常*快得多。 (3认同)
  • **总是更快** 是一个大胆的主张。但这在编译器和解释器理论(和定义)上太过深入。 (3认同)
  • 这并没有回答这个问题。 (2认同)