为什么在第一个解释器之前编写第一个编译器?


72

第一个编译器由Grace Hopper于1952年编写,而Lisp解释器由John McCarthy的学生Steve Russell于1958年编写。编写编译器似乎比解释器难得多。如果是这样,为什么第一个编译器比第一个解释器早六年编写?


67
因为当时对编译器的需求比对解释器的需求大
棘手怪胎

23
要编译第一个口译员?:P(解释器很复杂(比简单的编译器要复杂得多),而且解释效率很高(用一个简单的编译器就不那么容易了),用机器代码编写高效的解释器很
麻烦

4
如果您将CPU执行指令当作对它们的解释,这就是我所看到的方式(不过是通过硬件实现的),那么声称第一个解释器是在第一个编译器之后编写的主张就不成立了。顺便说一句,我知道这对问题/答案没有帮助。这只是对另一种观点的有趣评论。
Pedro Henrique A. Oliveira 2014年

11
McCarthy没有编写LISP解释器。麦卡锡发明了一种数学形式主义。史蒂夫·罗素(Steve Russell)当时是麻省理工学院AI实验室的一名学生,他意识到他可以编写翻译来执行麦卡锡的幻想,剩下的就是历史了。
约翰·斯特罗姆

5
实际上,我听说McCarthy认为LISP无法实现。拉塞尔意识到LISP手册a)的麦卡锡的通用功能解释器,b)是可以实现的。
约尔格W¯¯米塔格

Answers:


97

编写编译器似乎比解释器难得多。

今天也许是这样,但我要说的是,大约60年前并非如此。原因如下:

  • 使用解释器,您必须将其和程序都保留在内存中。在一个1kb的内存是一种奢侈的时代,保持运行中的内存占用量很小是关键。并且解释比运行已编译程序需要更多的内存。
  • 现代CPU极其复杂,具有大量的指令目录。因此,编写一个好的编译器确实是一个挑战。旧的CPU要简单得多,因此编译也更简单。
  • 现代语言比旧语言要复杂得多,因此即使编译器也要复杂得多。因此,旧语言将具有更简单的编译器。

65
如果没有编译器,则必须在汇编中编写解释器
棘手的怪胎

34
最早的人在哪里编译。(当人们是口译员时,我们不需要计算机)。然后,一定有人考虑过用一种编译语言编写一个编译器(他们仅有的一种,但是随着时间的流逝,我的人开始编译)。
ctrl-alt-delor 2014年

1
实际上....我最近读到60年代登月飞行中使用的阿波罗制导计算机有一个解释器: Wiki条目 “ ...该解释器提供了比AGC本身支持的更多的指令...”我想这个“解释器”是 与LISP之类的东西相比,它更像是嵌入在旧的Apple II计算机中的“ Sweet 16”解释器。
Calphool 2014年

6
@ratchetfreak确实如此。和第一个编译器一样。
RBarryYoung

3
@richard:当人们是口译/计算器时,他们的职称称为“计算机”。因此,当人们是口译员时,从字面上看,他们就是计算机。曼哈顿项目使用了数千台“计算机”(实际上是真正的职称),核物理学家通过邮件将计算结果发送到这些计算机上,以供执行。实际上,该项目还雇用了数学家,他们分解了工程师的计算结果,这些工程师根据物理学家的理论制定了计算方法,然后将其发送给“计算机”。
slebetman 2014年

42

基本要点是1950年代的计算硬件环境使得当时考虑到对计算机进行批处理的情况下,只有编译器才可行。

当时,更好的用户界面主要限于打孔卡和电传打印机。1961年,SAGE系统成为第一台计算机上的阴极射线管(CRT)显示器。因此,口译员的交互性直到很晚才成为首选或自然。

1950年代,许多计算机都使用前面板开关来加载指令,而输出只是一排灯/ LED,而到了1970年代,业余爱好者甚至使用前面板开关和LED。也许您对臭名昭著的Altair 8800很熟悉。

其他硬件限制也使解释器不可行。1950年代,计算机中主内存(例如RAM)的可用性极为有限。在半导体集成电路出现之前(直到1958年才出现),存储器仅限于磁芯存储器延迟线存储器,后者以(无前缀)进行测量。结合二级存储内存(例如磁盘或磁带)的速度慢,即使在加载要解释的程序之前,拥有很多用于解释器的内存,即使不是不可行的,也将被认为是浪费的。

当IBM的John Backus领导的团队在1954-57年创建FORTRAN编译器时,内存限制仍然是一个主要因素。这个创新的编译器之所以成功,仅仅是因为它是一个优化的编译器

1950年代,大多数计算机几乎没有任何操作系统,更不用说诸如动态链接和虚拟内存管理之类的现代功能,因此,当时解释器的想法过于激进和不切实际。

附录

1950年代的语言是原始的。他们只包括一个的操作一把,往往是由底层硬件的指示或他们的目标使用的问题定义的影响无论是。

当时,就我们今天所想到的计算机而言,计算机很少是通用计算机。他们无需重新构建就可以重新编程,这被认为是一个革命性的概念-以前人们一直使用机电机器(通常是计算器)来计算或计算答案(1950年代的大多数应用本质上都是数字的)。

从计算机科学的角度来看,编译器和解释器都是翻译器,实现起来的复杂度大致相等。


在分时出现之前,计算机是否使用过电传打字?我希望他们使用行式打印机或线轴到磁带。否则,在每行文本之后计算机将不得不等待1-8秒,这表示计算机本可以(但不是)进行有用的操作1-8秒。
超级猫

2
@supercat-是的,使用了电传打字机,但它们远非“交互式”。我编程的第一台计算机有一个包含18位字的内存-其中1K。内存本身是一个旋转的,因此,当您打开计算机电源时,必须等到内存加速运行。它连接了电传打字机,您可以A)从键盘输入或通过纸带阅读器读取的字符(从计算机的角度来看都是相同的),B)在“打印机”上写一个字符。电传打字机”。啊,美好的日子-美好的日子..!我的格林温暖了吗?!?!?:-)
Bob Jarvis 2014年

@BobJarvis:那是哪一年?
2014年

1
@supercat-1973年-但当时的计算机(教育数据产品公司的EDP-18)还是比较老的。尽管如此,这仍然是我们所拥有的(而且在70年代初,在高中时遇到任何与计算机相撞的现象都是不寻常的),因此我们认为这非常了不起。:-)
Bob Jarvis 2014年

2
这是一个比公认的更好更好的答案。
Jim Garrison 2014年

12

最初的编程语言非常简单(例如,没有递归),并且接近于本身很简单的机器架构。那时翻译是一个简单的过程

编译器比解释器要简单,因为它必须将用于程序执行的数据和用于解释源代码的表都保存在一起。和解释将采取更多的空间,为自己,为程序源代码和符号表。

内存可能太少了(出于成本和体系结构原因),编译器可能是覆盖操作系统的独立程序(我确实使用了其中一种)。为了运行已编译的程序,必须在编译后重新加载OS。...这清楚地表明,为实际工作运行口译员根本不是一种选择

确实,编译器要求的简单性使得编译器不是很好(完全考虑代码优化仍处于起步阶段)。至少在某些地方,直到60年代后期,手写的机器代码才比编译器生成的代码更有效。甚至有一个代码扩展率的概念,将已编译代码的大小与一个非常好的程序员的工作进行了比较。对于大多数(所有?)编译器来说,它通常大于1,这意味着较慢的程序,更重要的是,较大的程序需要更多的内存。在60年代,这仍然是一个问题。

编译器的兴趣在于简化编程,特别是对于非计算专家的用户,例如各个领域的科学家。这种兴趣不是代码性能。但是,程序员时间仍然被认为是廉价的资源。直到1975年至1980年,成本才花费在计算机上,那时成本从硬件转换为软件。这意味着即使编译器也没有被某些专业人士所重视

花费大量的计算机时间是使口译员失去资格的另一个原因,以至于这个想法对于大多数人来说都是可笑的。

Lisp的情况非常特殊,因为它是一种极其简单的语言,使它变得可行(并且计算机在58中变得更大了)。更重要的是,Lisp解释器是有关Lisp的自定义性(元循环性的概念证明,与任何可用性问题无关。

Lisp的成功很大程度上归因于这种事实,即它的自定义性使其成为研究编程结构和设计新语言(以及方便其用于符号计算)的出色测试平台。


3
我的,多么大胆
法拉普2014年

@Pharap那是不合适的风格吗?我的目的是使关键信息更容易找到,同时允许比简单的事实枚举更自由的样式。我必须承认,它在该SE网站上似乎并不是很有效。
babou 2014年

@ anonymous-downvoter不加解释性地投票表示某人可能是愚蠢的。但是,它没有说谁。
2014年

4

我不同意这个问题的前提。

霍珀将军的第一个编译器(A-0)更像是链接器或宏语言。她将子例程存储在磁带上(每个子例程都分配了一个编号),并且她的程序将被编写为子例程和参数的列表。编译器将从磁带中复制所请求的子例程,然后将它们重新排序为完整的程序。

她使用“编译”一词的含义与人们编辑一首诗选集的含义相同:将各种物品收集在一起。

据我所知,第一个编译器没有词法分析器或解析器,这使其成为现代编译器的遥远祖先。她后来创建了另一个编译器(B-0,又名FLOW-MATIC),其目标是使用更加类似于英语的语法,但直到1958年或1959年才与Lisp解释器同时完成。

因此,我认为问题本身是有误的。看来,编译器和解释器几乎同时在同时演化,这无疑是由于共享思想,而这在当时使许多科学家按照相同的思路进行思考。

此处提供引文的更好答案:https : //stackoverflow.com/a/7719098/122763


3

等式的另一部分是,编译器是汇编器之上的抽象步骤。首先,我们使用硬编码的机器代码。“我们”是组装者。手工将每个跳转和偏移量等计算为十六进制(或八进制),然后打入纸带或卡片中。因此,当装配工出现在现场时,节省了很多时间。下一步是宏汇编程序。这样就可以编写一个宏,并将其扩展为一组机器指令。因此,Fortran和Cobol向前迈出了一大步。资源(存储,内存和cpu周期)的缺乏意味着通用解释器必须等待技术发展。大多数早期的解释器都是字节码引擎(例如当今的Java或CLR,但功能要少得多)。UCSD Pascal是一种非常流行(且快速)的语言。MS Basic是引擎盖下的字节码引擎。

在指令开销方面,它完全取决于正在运行的处理器。一段时间以来,该行业经历了RISC与CISC的巨大动荡。我亲自为IBM,Data General,Motorola,Intel(当他们出现时),TI和其他一些人编写了汇编程序。指令集,寄存器等之间存在相当大的差异,这将影响“解释” p代码的要求。

作为时间参考,我于1972年左右开始在电话公司编程。


2

如果您没有将所有内容保存在内存中,则编译后的代码会更快。别忘了,在这些时候,函数已经加入了已编译的代码。如果不进行编译,则不知道需要什么功能。因此,您是从...调用函数的。哦,还不是从磁盘开始,我们处于50年代初期,但是从卡上开始!在运行时!

当然,可以找到一种解决方法,但是由于语言非常简单并且与机器代码相距不远,因此尚未找到解决方法。而且编译很容易并且足够。


1

在编写第一个编译器之前,人们编写了汇编代码,与普通的二进制代码相比,这是一个巨大的进步。当时,有一个强有力的论点是,由编译器编译的代码的效率将低于汇编代码-那时(计算机成本)与(程序员成本)的关系与今天有很大不同。因此,从这个角度来看,对编译器有很强的抵抗力。

但是编译器比解释器高效得多。如果您当时建议编写口译员,人们会认为您疯了。您能想象购买一台价值一百万美元的计算机,然后浪费其90%的代码解释能力吗?


我对1950年代的计算机了解不多,但是至少对于后来的几十年的冯·诺依曼机器而言,每个解释指令的解释器开销将是2到5条指令(也许总共4到10个周期):解码,间接跳转,也许解码其他参数。如果您将解释后的指令集设置为足够高的级别(因此,每个解释器指令要执行几条机器指令),则该比例将小于90%,甚至可以接受。另请参阅:第四。

3
在编写第一个编译器之前,大多数程序实际上都是用实际的机器代码而不是汇编语言(op-codes助记符)编写的。
mctylr 2014年

2
@delnan那些系统的时钟以千赫兹为单位运行,因此浪费3-5个时间来降低性能可能是由于系统由于硬件故障(例如,真空管烧断)而失败之前成功完成程序之间的差异。如果我没记错的话,硬件故障在1950年代每天都在发生。
mctylr 2014年

delnan:给我看一个可以解释5条指令开销的解释器。Forth不是一种解释语言,它是一种可憎的:-)。抱歉,我的意思是线程语言。诸如BASIC之类的东西需要花费无数的指令来解释一个语句。
gnasher729 2014年

@gnasher:1974年,我为Keronix(ahem,Data General Nova克隆)构建了高性能的BASIC解释器,该解释器使用面向字节的P代码对BASIC语句进行编码。大约需要25-50条机器指令来“解释” pCode,其中10条用于字节提取本身。(我在1969年做了一个基于令牌的令牌,花了更多时间,因为它实际上必须重新解析表达式)。但是它既不是也不是“千亿万个”。
艾拉·巴克斯特

1

在解释循环程序之前,必须将其存储在可以重复读取的介质中。在大多数情况下,唯一合适的介质是RAM。由于代码通常会输入到打孔卡上(对于人类可读的语言而言)很可能是空的,因此在对代码进行存储之前必须对其进行某种处理。在许多情况下,将打孔卡文本处理成适合于处理器直接执行的形式,实际上并不比将其处理成可以通过解释器有效处理的形式更困难。

请注意,早期编译器的目标不是在磁盘上生成汇编语言或目标代码文件,而是在准备好执行的RAM中存储代码。当没有操作系统妨碍时,这实际上非常容易。编译器可以从内存的一端开始生成代码,并从另一端开始分配变量和分支目标。如果一条语句标记有标签“ 1234”,则编译器将在变量“ 1234”中存储一条指令,以跳转到当前代码生成地址,如果该变量不存在,则创建该变量。语句“ goto 1234”(如果不存在)将创建一个变量“ 1234”,然后跳转到该变量(希望在执行该语句之前跳转到存储在其中的正确位置)。goto一个尚未定义的标签,因为它知道何时goto编译将跳转到一个变量。这可能不是生成代码的最有效方法,但是对于希望计算机处理的程序大小来说已经足够了。


1
磁盘?嗯...不 胶带。大而慢的卷到卷磁带。还有很多。我记得要去参加格雷斯·默里·霍珀(Grace Murray Hopper)的演讲(对我而言,这很有趣,因为我是系统分析专业的学生,​​也是校园海军ROTC支队的中尉,而CAPT Hopper是在职海军军官)。她讲述了一个故事,她说,她想到了将程序的未使用部分写入磁带的想法-她称其为“使用辅助存储”。她说:“但是,直到IBM做同样的事情并将其称为虚拟内存之后,这个想法才流行起来。” CAPT Hopper真的不喜欢IBM ... :-)
Bob Jarvis 2014年

@BobJarvis:我没有考虑过这方面,但是将用于将准备运行的代码编译到RAM的相同的通用一遍设计可以与磁带驱动器一起很好地工作。编译器的输出将进入磁带,然后将磁带倒带并读入顺序内存地址并直接运行,而无需将编译器的任何部分同时存储在内存中。
超级猫

0

因为解释器需要编译器才能起作用。

上面的说法并不是真的。严格来说,您可以在不使用编译器或与之不进行交互的情况下进行解释器。但是这样做的结果看起来并不太像我认为这些术语所指的意思。

从严格意义上讲,编译器和解释器做的事情完全不同。编译器从某些来源读取文本,并将其转换为其他格式:汇编语言,机器代码,另一种高级语言,数据结构或其他任何格式。同时,解释器采用某种数据结构,并根据该数据结构执行指令。

如今,我们通常认为的“编译器”实际上是已与代码生成器配对的编译器:该程序从某个来源获取数据,并根据所看到的内容以某种格式输出代码。对于编译器来说,这是相当直观的用法,这是为编译器创建的第一批东西。但是,如果换一种方式来看,这似乎与解释器的工作非常相似。它总是输出代码而不是执行更多常规操作,但是原理是相同的。

如果我们从另一侧看,则解释器需要从某处获取其数据。这只是数据,因此您可以采用与构建其他任何类型的数据相同的方式来构建数据。由于我们正在讨论解释,因此您自然可以根据文本文件中的指令来构建数据。但是要做到这一点,您需要在文本中读取内容并创建数据结构,这就是一个编译器。它被连接到解释器,而不是代码生成器,但是它是一个编译器。

这就是为什么首先编写编译器的原因。即使构想了编译器,解释数据结构的想法也不是什么新鲜事,但是编译器是“缺失的链接”,它使程序员可以用文本构建这些结构。


据我了解,Apple的原始Basic解释器是手动翻译成机器代码的,从来没有以机器可读的源代码格式存在。我不确定您会说使用了什么“编译器”来生产它。
2014年

@supercat:我不知道您提到的Basic解释器是如何产生的,但是我不是在谈论用于产生解释器的编译器。我说的是解释器的一部分。作为其代码的一部分,Apple] [BASIC解释器需要某种方式来读取包含BASIC代码的文本文件,并从该文本创建语法树,然后执行该语法树。执行此操作的代码是编译器;它不会生成机器代码,但仍会执行读取代码并将其转换为另一种形式的任务。
2014年

典型的微型计算机BASIC解释器甚至不会产生类似于语法树的任何内容。我不熟悉原始的Apple BASIC解释器(称为“整数BASIC”),但是Microsoft为Apple(“ Applesoft BASIC”),Commodore和其他各种公司实现的BASIC解释器按原样存储程序文本,除了两件事:(1)每行前都有一个16位行号和下一行的16位地址,后跟一个零字节;(2)将每个关键字替换为设置了高位的单个字节。除此之外,还对表达式进行了解析……
超级猫

从左到右的每个字符 像A = 1234“这样的语句会在运行时将数字1、2、3和4转换为浮点数
。– supercat

@supercat:听起来比语法树更接近字节码,所以我对此有误。但是要点仍然存在:伪字节码仍必须由文本构造,而执行此操作的代码是编译器。
2014年

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.