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


8

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


9
这意味着C是一种编译语言,而Bash是一种解释语言。仅此而已。
Radon Rosborough

10
我投票结束这个问题是离题的,因为它与U&L无关,即使它涉及的范围太广,人们也可以无休止地讨论这个问题,而没有提供令人满意的答案,而不是说一个适合该问题的答案。 U&L的总体思想是解决问题的知识库。
反模式

4
@countermode您堆叠(交换|溢出)乡亲和您满意的触发投票。问题并不广泛:它揭示了一个非常具体的缺少的理解:编译语言和解释语言之间的区别。这需要几个段落来解释,另外两个段落指出它们各自的(缺点)优势,再加上一个脚注以总结C和bash具有不同的目标,因此他们选择了不同的方法。
mtraceur '16

2
@mtraceur抱歉,我不是要冒犯任何人。就触发满意的封闭式投票而言,这有点不公平。结束一个问题需要五票,如果没有人投票不结束,我会满意的。您对这个问题绝对正确,但是根据unix.stackexchange.com/help/on-topic,我不确定此类问题是否属于U&L 。
反模式

2
@mtraceur如果您认为范围不广,我不同意您的意见。关于编译器/解释器/等之间的区别,有很多要说的。和解释/编译语言。有容易击中的酒桶。此外,该问题更适合“计算机科学”或“程序员” SE。
MatthewRock '16

Answers:


23

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

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

某些语言通常同时具有解释器和编译器:BASIC是一个让人想到的示例。

您还可以找到所谓的Shell脚本编译器,但我所见过的只是混淆了包装程序:它们仍然使用Shell来实际解释脚本。正如mtraceur指出的那样,尽管适当的shell脚本编译器当然是可能的,但并不是很有趣。

考虑这种情况的另一种方法是认为外壳程序的脚本解释功能是其命令行处理功能的扩展,这自然会导致一种解释方法。另一方面,C旨在产生独立的二进制文件;这导致了一种编译方法。通常编译的语言也往往会发芽解释器,或者至少是命令行解析器(称为REPL,read-eval-print循环; shell本身就是REPL)。


1
许多脚本语言的“编译器”只是包装器。我记得一个BASIC编译器,它将解释器可执行文件与压缩的源代码连接在一起。
德米特里·格里戈里耶夫

@DmitryGrigoryev我可以轻易相信这一点!对于BASIC,我正在考虑诸如Turbo Basic之类的编译器。我认为有一些真正的DOS BAT文件编译器,但我可能会误会!还有常见的p代码方法...
Stephen Kitt

1
请注意,完全有可能为外壳使用“ true”编译器。这只是一般不值得努力/复杂性,因为一个天真的变异只会产生一个程序,通常要求一堆execveopenclosereadwrite,和pipe系统调用,穿插了一些getenvsetenv和内部的HashMap /阵列操作(未导出的变量)等。Bourneshell和派生类也不是从低级编译器调整(例如代码重新排序等
受益最多的

@StephenKitt,如果我提出与您的答案相关的新问题,您会介意吗。虽然您的回答说明很多。但也给我提出了一个新问题。
Mongrel,2013年

可以肯定,我会提出一个新问题。我以为你可能想让我在聊天中问这个问题。因为它与您的答案有关。
Mongrel,2013年

6

考虑以下程序:

2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes

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.

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

这是一个独立的程序,不需要“有经验的购物者”即可执行。它所需要的只是具有“基本人性操作系统”的人。

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

解释器和编译器都有其优点和缺点。


3
很好的类比...还解释了为什么您可以在不同的体系结构上使用相同的购物清单,但不能使用相同的机器代码(如此!)–所有东西都位于不同的位置,等等。尽管如此,您可能需要不同的“有经验的购物者”谁知道另外一家超市
彼得-恢复莫妮卡

6

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

一,技术差异

(注意:为了解决这个问题,我在这里做了很多简化。为了更深入地理解,我回答底部的技术说明详细阐述/完善了此处的一些简化方式,并且对此答案的评论中有一些有用的说明和讨论。)

基本上有两种编程语言类别:

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

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

bash在第二类中(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 解释或bash 编译器。没有什么能阻止这种可能性的发生:仅仅是那些语言是为不同的目的而制作的。仅用另一种语言重写程序通常比为复杂的编程语言编写好的解释器或编译器容易。尤其是当这些语言具有特定的功能时,它们就很擅长,并且首先以某种特定的工作方式进行设计。C被设计为可编译的,因此它在交互式外壳程序中缺少许多您想要的方便快捷方式,但是它对于表达非常特定的,低级的数据/内存操作以及与操作系统的交互非常有用。 ,这是您要编写高效的已编译代码时经常发现自己要执行的任务。同时,bash非常擅长执行其他程序,

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

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

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

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


我不明白为什么这个问题被否决了。对于一个广泛而又不太清楚的问题,这是一个令人印象深刻的全面答案。
Anthony Geoghegan

编译器将一种语言翻译成另一种语言。它不必编译为机器语言。您可以使用字节码编译器。Java到ASM编译器,等等。C语言的制造商不想要最强大的功能,他们想要适合他们需求的语言。C可以在某种程度上解释。Bash可以编译-有shc。尽管遵循了一些约定,但大多数时候是否编译/解释语言取决于您使用的工具,而不取决于语言本身。
MatthewRock '16

@MatthewRock我添加了一个技术说明来解决编译成不必要的机器语言问题。我觉得我的第一个技术说明已经涵盖了“可以解释C ...可以编译Bash”的内容。我有一个解决“ C语言制造商不想要最大功率”问题的想法,尽管我认为很明显,他们设计的语言在当时使用的硬件上效率很高(空字节字符串终止符之所以在某种程度上是由于这样的事实,毕竟,这样做会让您在那些字符串上迭代时少使用一个寄存器。
mtraceur

@MatthewRock(续)我认为可以很公平地说C的制造商并不完全想要功能,但是他们确实想抽象OS编写的工作,尤其是在那些日子里,代码效率非常重要。否则他们将需要在汇编器中完成这项工作。因此,他们开发了一种与他们为其编写代码的PDP机器所使用的机器代码紧密相关的语言,这至少作为一种副作用,即使不是作为显着的设计目标,也可以提高该平台的效率,即使是天真的非优化编译器。
mtraceur '16

他们不会在汇编器中这样做。他们有。到处都是陷阱,这里不再是问题。总体而言,答案可能会回答问题,但仍有一些细节不准确。
MatthewRock '16

5

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

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

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


1
在bash脚本中,执行时也会出错。但是我们不编译它。
Mongrel,2013年

30
总是一个危险的词……
Radon Rosborough

3
优化的编译CPython通常比优化的asm.js(JavaScript的子集)要慢。因此,有一个例子,它不是更快,因此也不是“总是”更快。但是,通常速度要快得多。
wizzwizz4 2016年

2
这不能回答问题。
mathreadler '16

3
总是更快是一个大胆的主张。但是,这在编译器和解释器理论(以及定义)上有太多的深度。
贾科莫·卡泰纳齐

3

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

您可能会通过3种方式来阅读:

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

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

脚本语言(例如Bash)通常分为1类和2类。它们一次占用一行,翻译并运行该行,然后移至下一行。在Mac和Linux上,默认情况下针对不同的语言(例如Bash,Python和Perl)安装了许多不同的解释器。在Windows上,您必须自己安装这些。

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

最后,编译语言(如C)在运行它们之前先翻译整个程序。这样做的好处是,转换可以在与执行不同的机器上完成,因此,当您将程序提供给用户时,尽管可能仍然存在错误,但已经可以清除几种类型的错误。就像您将其提供给翻译人员一样,并且我提到了garboola mizene resplunks,这对您来说似乎是有效的英语,但是翻译人员可以告诉您我在胡说八道。运行编译的程序时,它不需要解释器-它已经在计算机的本地语言中

但是,编译语言有一个缺点:我提到计算机有一种本地语言,由硬件和操作系统的功能组成-好吧,如果您在Windows上编译程序,则不会期望编译后的程序在Mac。一些语言通过编译为一种中途语言来解决此问题-有点像Pidgin英语-这样,您可以获得编译语言的好处,并且速度有所提高,但这确实意味着您需要捆绑带有您的代码的解释器(或使用已安装的代码)。

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


这个答案很好,但是我认为Perl“解释器”使用的动态编译与通常所说的“ JIT”不同,因此最好避免使用该术语。JIT通常用于指代从已编译的字节码到目标机器码(例如JVM,.Net CLR)的即时编译。
IMSoP '16

@IMSoP是的,字节编译的语言和JIT从字节码编译的语言确实是不同的东西。我认为可能值得一提(我在回答的脚注中简短提及了它),但我认为值得一提的是JIT的总体思路是,它有可能介于“已编译”之间和“解释”,即部分编译和部分解释。就是说,对于不确定还不了解编译/解释之间区别的人(例如OP),我不确定这是否更有价值或令人困惑/分散注意力。
mtraceur's

@mtraceur坦白地说,即使我对PHP 7,Perl 5,.Net和Java的模型之间的区别也有些困惑。对于初学者来说,最好的总结可能是“有多种混合使用方式来混合编译和解释的方法,包括使用中间表示,以及在程序运行时编译或重新编译块”。
IMSoP '16

@IMSoP我同意。那是我回答问题时使用的一种方法,您的这一评论为我提供了进一步改进方法的想法。所以谢谢。
mtraceur

@IMSoP JIT不仅发生在字节码上-例如,node.js具有一些JIT功能。但我同意-为了简单起见,我将它们全部都标记在“ JIT”下,因为这就像一个初学者的问题-我正在对其进行编辑以使用更简单的术语。
user208769 '16

1

很多人都在谈论解释与编译,但是我认为如果仔细观察它可能会产生误导,因为某些解释语言实际上是在执行之前被编译为中间字节码。

最后,将C程序需要编译为可执行格式的真正原因是,计算机需要做大量工作才能将C源文件中的代码转换为可以运行的内容,因此保存产品很有意义将所有这些工作都存储到可执行文件中,因此您无需在每次运行程序时都再次执行该操作。

另一方面,Shell解释器只需要做很少的工作即可将Shell脚本转换为“机器操作”。基本上只需要逐行读取脚本,在空白处分割脚本,设置一些文件重定向和管道,然后执行fork + exec。由于与在Shell脚本中启动进程相比,解析和处理Shell脚本的文本输入的开销非常小,因此将Shell脚本编译为中间机器格式而不是仅仅解释会显得过大。源代码直接。


+1,尽管我确实发现自己想知道这是否不是“把马放到马前”:也许最初的外壳最初是旨在以交互方式可用的,因此开销较低,足以不麻烦编译,并且相对简单只是基于此的设计决策?
mtraceur

是的,这是研究这个问题的另一种方式:)
hugomg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.