C语言发展简史17页word资料Word文档格式.docx
《C语言发展简史17页word资料Word文档格式.docx》由会员分享,可在线阅读,更多相关《C语言发展简史17页word资料Word文档格式.docx(12页珍藏版)》请在冰豆网上搜索。
这基本上是目前关于C语言的最新、最权威的定义了。
现在,各种C编译器都提供了C89(C90)的完整支持,对C99还只提供了部分支持,还有一部分提供了对某些K&
RC风格的支持。
C语言的发展史
DennisM.RitchieTjy平坦软件园
BellLabs/LucentTechnologiesTjy平坦软件园
MurrayHill,NJ07974USATjy平坦软件园
dmr@bell-labs
概要
C语言作为最初的Unix操作系统的系统实现语言,在1970年早期诞生。
它以无类型的B语言为基础,形成了一个类型结构;
它在小型机上被发明,作为一个工具,以改进贫乏的编程环境。
在今天,它已经成为一种占统治地位的语言。
本文研究它的发展。
Tjy平坦软件园
简介
版权提示:
版权所有1993计算机协会公司。
此电子版由作者授权发表。
有关出版事宜,请联系ACM或作者。
本文被用于一九九三年四月,在马萨诸塞州剑桥的第二次编程语言历史会议上的演讲。
之后被收录进会议记录:
编程语言历史,第二版。
ThomasJ.Bergin,Jr.andRichardG.Gibson,Jr.ACMPress(NewYork)andAddison-Wesley(Reading,Mass),1996;
ISBN0-201-89502-1.
本文是关于C程序设计语言的发展、它所受到的影响以及它诞生的条件。
为简洁起见,我略过了对C本身完整的描述、它的起源B[Johnson73]及更上一代BCPL[Richards79],相反集中关注每一种语言的典型特性,以及他们如何发展变化。
C在1969——1973年间与Unix操作系统同时诞生;
最富创造性的时期是1972年。
另一次大的变化发生在1977到1979年间,当Unix系统的可移植性得到证明时。
在后一段时期的中间,这个语言的第一个被广为传播的描述出现了:
C程序设计语言,常常被称为白皮书或K&
R[Kernighan78]。
最后,在1980年代中期,它被ANSIX3J11委员会正式标准化,作了进一步修改。
直到1980年代早期,尽管编译器已在多种机器结构及操作系统上出现,这个语言几乎与Unix特别密切关联;
更近一些,它的使用传播得更广,并且在今天它几乎是计算机行业被使用最多的语言。
历史:
环境
贝尔电话实验室的计算机系统研究在1960代年晚期是乱糟糟的[Ritchie78][Ritchie84]。
贝尔电话实验室公司正从他和麻省理工学院、通用电气公司的合作项目Multics[Organick78]撤离。
到1969年,贝尔实验室管理层和研究人员认为,Multics项目不能按期完成并且代价高昂。
在GE-645Multics机器被撤走之前,KenThompson领导一个非正式小组,开始一些其它的研究。
Thompson希望按自己的设计,使用一切有效的方式,创造一个适当的计算环境。
他的计划,回想起来是清晰的。
组合Multics中的许多创新特征,包括一个作为控制场所的进程的清晰概念、一个树结构文件系统、一个作为用户级程序的命令解释器、文本文件的简单表示和访问设备的通用化。
他们排除其余特性,比如对内存和文件的统一访问。
开始,他与我们其余人推迟了Multics中的另一个先锋性(但非原创)特征,即只使用高级语言来编写。
我对Multics实现所用的语言PL/I,并无兴趣,但我们还使用其他语言,包括BCPL,我们对于不能利用汇编之上的高级语言进行编程的优点,比如容易编写、易于理解,感到遗憾。
当时我们并未特别注重可移植性;
到后来才有了这方面的兴趣。
Thompson面临的是那个时代古怪和难缠的硬件:
DECPDP-7,他在1968年开始使用时,只有8K容量的16位内存,并且没有可用的软件。
当时他希望使用高级语言,但还是用PDP-7汇编编写了最初的Unix系统。
开始,他并未在PDP-7上编码,相反使用一套GE-635机器上,用于GEMAP汇编器的宏。
一个后期处理器生成PDP-7可读的纸带。
这些纸带从GE机器传送到PDP-7进行测试,直到一个原始的Unix内核、一个编辑器、一个汇编器、一个简单的shell(命令解析器),和其它工具(像Unixrm,cat,cp命令)被完成。
此后,这个操作系统可以自我支撑:
可以编写、测试程序勿需借助纸带,并且程序开发可以在PDP-7上继续进行。
Thompson的PDP-7汇编器在简明性上甚至优于DEC的;
它对表达式求值并得到对应的数据位。
没有库、装载器或链接器:
程序的全部源文件被送给汇编器,输出文件——有一个固定名字——产生后被直接执行(这个名字,a.out,解释了一点Unix的渊源;
它是汇编器的输出。
甚至在系统有了链接器和显式指定另一个名字的方式之后,它仍被保留作为编译的默认可执行文件)。
在Unix首次在PDP-7运行后不久,DougMcIlroy在1969年创造了新系统的第一个新语言:
一个McClure的TMG[McClure65]实现。
TMG是一种编写编译器(更普通来说,TransMoGrifiers)的语言,通过在一个混合过程元素,上下文无关的语法标记的自顶而下,递归降解的风格。
McIlroy和BobMorris使用TMG为Multics编写了早期的PL/I编译器。
为了挑战McIlroy重新生成TMG的技巧,Thmopson决定Unix——当时可能还没有取这个名字——需要一种系统编程语言。
在很快用Fortran尝试一番后,相反,他创造了一种他自己的语言,他命名为B。
B可以认为是没有类型的C。
更准确,它是被挤进了8K字节内存,经过Thompson大脑过滤的BCPL。
它的名字最有可能表示BCPL的缩写,尽管另一种理论认为它继承自Bon[Thompson69],一个Thompson在Multics的那些日子创造的不相关的语言。
Bon可能是以他妻子Bonnie的名字,或者(根据它的手册中的一个encyclopedia引用)以一种具有咕隆咕隆发音的神奇仪式的宗教命名的。
起源:
语言
BCPL由MartinRichards于1960年代中期在访问麻省理工学院时设计,在1970年代早期被用在几个有趣的项目中,其中包括牛津大学的OS6操作系统[Stoy72],和施乐公司PARC研究中心创造性的Alto上的部分工作[Thacker79]。
因为Richards工作过的麻省理工学院的CTSS系统[Corbato62]被用于Multics开发,我们也熟悉该语言。
最初的BCPL编译器被RuddCanaday和贝尔实验室的一些人们迁移到Multics和GE-635GECOS系统[Canaday69];
在Multics的生命在贝尔实验室的最后痛苦挣扎中,它很快成了那些以后参与Unix的人们选择的语言。
BCPL,B和C全都严格符合以Fortran和Algol60代表的传统过程类型语言。
它们都面向系统编程、小、定义简洁,以及可被简单编译器翻译。
它们接近机器,它们引入的抽象以传统计算机提供的具体数据类型和操作为基础,它们依赖于输入输出库例程,与操作系统的其它交互。
尽管并未成功,它们还使用库程序指定其他有趣的控制结构,如协程和过程关闭。
同时,它们的抽象层次足够高,足够用心的话,能达到机器间的可移植性。
BCPL,B和C在语法上差异众多,粗略地说,它们是相似的。
程序由全局声明和函数(过程)声明组成。
BCPL中的过程能够嵌套,但不能引用包含过程中的非静态对象。
B和C避免了这个限制,通过强加一个更严格限制:
完全没有嵌套过程。
每一种语言(除了早期版本B)都认可分离编译,以及提供了包含指定文件文本的方式。
BCPL中的几个语法和词法机制是优雅和常见的,甚于B和C中的那些。
例如,BCPL的过程和数据声明有更一致的结构,并且它提供了一套更完整的循环构造。
尽管BCPL程序在概念上是由未被间隔的字符流,聪明的规则允许语句后的行分界处的大多数分号被忽略。
B和C忽略了这种便利,并以分号来结束大多数语句。
不管这些差异,BCPL的大多数语句和操作符直接对应B和C中的相应语句和操作符。
BCPL和B之间的一些结构化的差异源于介质存储的限制。
比如,BCPL声明采用这样的形式Tjy平坦软件园
letP1becommandandP2becommandandP3becommand...此处的命令表示的程序文本包含完整过程。
关联的子声明同时出现,所以名字P3在guochengP1内可见。
相似地,BCPL能在一个求得一个值的表达式里包含一组声明和语句,例如Tjy平坦软件园
E1:
=valof(declarations;
commands;
resultisE2)+1BCPL编译器可以容易地处理此类构造,在产生输出前,通过存储和分析内存中一个完整程序解析过的表示。
B编译器的存储限制要求一个一步技术,通过它尽可能快生成输出,语法上的重新设计,令这种可能迈进了C。
BCPL中一些不令人满意的地方归因于它的技术问题,在B的设计中它们被有意识的避免了。
例如,BCPL使用一个“全局向量”(globalvector)机制以在分离编译的程序间通信。
在这种模式中,程序员使用一个全局向量的数值偏移量,显式关联每个外部可见过程和数据对象的名字。
链接使用这些数值偏移量,在被编译过的代码上完成。
B起初坚持,整个程序一次性全部传递给编译器,来规避这个麻烦。
B的后期实现,和C的全部实现,使用一个传统的链接器,来解决出现在分离编译文件中的外部名字,而不是把指定偏移量的负担推给程序员。
BCPL到B的转换中引入的其它变化,大概是因为风格的缘故,一些仍是有争议的,例如赋值使用单个字符=代替:
=。
类似地,B使用/**/来括起注释,而B使用//注释直至行末的文本。
这显然是从PL/I继承来的。
(C++重新启用了BCPL的注释惯例。
)Fortran影响了声明的语法:
B的声明以一个auto,static这样的类型指定符开始,跟着一列名字,C不仅遵循这种风格,还把它的类型关键字,加入这种声明的开始处。
在Richards的书中文档化的BCPL与B之间的差别,并非都是经过深思熟虑的;
我们是从一个BCPL[Richards79]的早期版本开始工作的。
例如,用于跳离switchon语句的endcase在我们1960年代开始学习该语言时,并没有出现,所以B和C中重复出现的,用于跳离switch语句的关键字break,乃是一种背离的发展,而不是清醒的改变。
对比B产生过程中发生的普遍的语法变化,BCPL的核心语义内容——类型结构和表达式求值——保持不变。
它们两种语言都是无类型的,或更恰当地说有一种单一的数据类型,“字”(word)或“单元”(cell),一个固定长度的位模式。
这些语言中的内存由此类单元的线形数组组成,每一个单元的内容的含义与应用的运算相关。
例如,求和运算符使用机器的整数加法指令,简单相加其运算对象,其它算术运算同样不清楚它们运算对象的含义。
因为内存是一个线形数组,只可能解析单元的值为该数组的索引,并且BCPL为这个目的提供一个运算符。
在最初的语言中,它被拼写为rv,后来为!
,但是B使用一元*。
因此,如果p是单元,包含另一个单元的索引(其地址,或指向的指针),*p引用被指向单元的内容,作为表达式的值或赋值对象。
因为指针在BCPL和B中只不过是整型内存数组的索引,对它们进行算术运算是有意义的:
如果p是一个单元的地址,那么p+1是下一个单元的地址。
这种约定是两种语言中数组语义的基础。
在BCPL中,一个人这样写Tjy平坦软件园
letV=vec10或在B中,Tjy平坦软件园
autoV[10];
效果是一样的:
分配了一个名字为V的单元,然后保留另一组10个连续单元,它们中第一个的内村索引,被存放在V中。
按照一般的规则,在B中的表达式Tjy平坦软件园
*(V+i)把V和i相加,并指向V后第i个位置。
BCPL和B都增加了特别的符号,使这种对数组的访问更简洁;
在B中的等价表达式是Tjy平坦软件园
V[i]在BCPL中是Tjy平坦软件园
V!
i这种引用数组的方法甚至在当时仍是不常见的;
C后来同化它为一种更不常规的方式。
BCPL,B或C都没有强烈支持字符数据;
每一个都把字符串当作整型数组,并通过一些惯例提供了一些一般规则。
字符串字面值在BCPL和B中表示一个使用串内字符初始化的静态区的地址,被包装成单元。
在BCPL中,第一个被包装的单元包含串所拥有的字符个数;
在B中,没有此计数,字符串以一个特别的字符终结,在B中杯拼写为“*e”。
这个改变部分是为了避免把计数值放在一个8位或9位槽(slot)产生的串长度限制,部分是因为维护这个计数,从我们的经验看来,不如使用一个终结符方便。
在BCPL,串中每个字符的使用,是通过被展开为另一个数组,一个字符对应一个单元,然后进行再次包装;
B提供了对应的例程,但人们更多地使用,另外的访问或替换一个串内字符的库函数。
更多历史
在TMG版本B工作后,Thompson利用B重写了B(编译器)(一个bootstrapping步骤)。
在开发中,他不断与内存限制作斗争:
每次语言版本使编译器膨胀令内存几乎不够使用,但每次重写利用语言特征的优点,减少了它的尺寸。
例如,B引入通用赋值运算符,使用x=+y来把y加入x。
这个符号经过McIlroy引自Algol68[Wijngaarden75],他将它合并到他实现的一个TMG版本。
(在B和早期C,该运算符被拼作=+而不是+=;
这个由B的词法分析的第一种形式的迷惑捷径导致的错误,在1967年被修复。
)Tjy平坦软件园
Thompson通过发明自增++和自减--运算符,走出了更深远的一步;
它们的前缀或后缀位置决定变更是发生在计算运算对象值之前或之后。
它们没有出现在B的最早版本中,而是随后才出现的。
人们经常猜测,它们被创造是为了使用,C和Unix在其上首次流行的DECPDP-11提供的自增和自减地址模式。
这在历史上来说是不可能的,因为B被发明的时候还没有PDP-11。
PDP-7有一些“自增”内存单元,使用这种特性,一个间接内存引用通过它们来自增单元。
这些特征可能提示Thompson创造了那些自增运算符;
他把前缀和后缀一般化。
甚至,自增单元没有被直接用于实现这些运算符,并且这种创新一个更强烈的动机可能是,他发觉++x的翻译在尺寸上小于x=x+1。
PDP-7上的B编译器不产生机器指令,而是一个由编译器输出组成代码段地址序列,执行基本运算的解释模式的threaded代码[Bell72]。
这些操作——特别对B——典型地运行在一个简单堆栈机器上。
在PDP-7的Unix系统上,除了B本身只有几个东西是B写的,因为这个机器太小和太慢,除了试验而不能做更多事情;
完全用B重写操作系统和其它应用程序,是看起来不可行的代价高昂的动作。
Thompson在某些地方,通过提供一个利用换页解释器代码和数据,允许解释超过8K字节的程序的“虚拟B”编译器,来释放地址空间,但它对通用程序来说太慢以致不实用。
尽管如此,一些用B写的工具还是出现了,包括一个早期版本的,Unix用户熟悉的可变精度计算器dc[McIlroy79]。
我做的最有雄心壮志的工作,是一个把B翻译为GE-635机器指令而非threaded代码的真正的交叉编译器。
它是一个精巧的绝技:
一个用本身语言写的,生成在一个,在有4k字长用户地址空间的18位机器上运行的36位大型机代码,完全的B编译器。
这个项目能实现,仅仅是因为B的简单性和它的运行时系统。
尽管我们抱有关于实现一个那时,像Fortran,PL/I或Algol68的主要语言的偶然想法。
这样的项目对我们的显得绝望的大:
需要更简单和小的工具。
所有这些语言都影响我们的工作,但是凭我们自己之力来做这些事情则更有趣。
到1970年时,我们看起来能在Unix项目上,获得一个新的DECPDP-11。
处理器是DEC递交的第一批产品,三个月后,磁盘才到达。
通过threaded技巧,使B程序在其上运行只需要为运算符重写代码段,和一个我用B写的简单的汇编器。
很快,dc成了在其它操作系统之前,第一个在我们的PDP-11上被测试的有趣的程序。
几乎非常快,但仍需等待磁盘,Thompson用PDP-11汇编语言,重写了Unix内核和一些基本命令。
最早的PDP-11上的Unix把机器上24K内存中的12K给操作系统,一个很小的空间给用户程序,其余的作为RAM磁盘。
这一版本仅是用于测试,而不是实际的工作;
这个机器通过枚举关闭的,knight的不同尺寸象棋板的路程,来标记时间。
在磁盘到达后,我们把汇编语言转换为PDP-11上的方言,和移植一些B程序,很快移植到它上面去。
到1971年时,我们的微型计算机中心开始有了用户。
我们都希望更容易编写有趣的软件。
使用汇编显得沉闷,B不管它的性能问题,已经有了一个小的包含有用服务例程的库,并且被用于越来越多的新程序。
这段时期的最著名的成果,是SteveJohnson的yacc分析——生成器[Johnson79a]的第一个版本。
B的问题
我们第一次使用BCPL然后是B的机器,是按字寻址的,这些语言的单一数据类型,“单元”,能恰当与硬件机器字互相换算。
PDP-11的出现暴露了B的语义模型的一些不足。
首先,它从BCPL继承的几乎未作改变的字符处理机制是笨拙的:
使用库方法把包装的字符串展开到单个的单元,然后再次包装,或者访问或替换单个字符,在一个面向字节的机器上,开始变得笨拙,甚至愚蠢。
其次,尽管最初的PDP-11没有提供浮点算术运算,制造商承诺将很快提供。
浮点运算通过定义特别的运算符,被添加到我们的Multics和GCOS的B编译器,但是这种机制仅在相应的机器上才可能,单个字长足够包含一个浮点数;
这在16位PDP-11上是不成立的。
最后,B和BCPL模型在处理指针时,暗中会做得更多:
语言规则,通过定义一个指针作为字数组的索引,强迫指针被表示为字索引。
每个指针引用生成一个运行时,从指针到硬件要求的字节地址的度量转换。
因为这些理由,看起来需要一个类型模式来处理字符和字节寻址,以及为即将到来的浮点硬件作准备。
其它问题,特别是类型安全性和接口检查,看起来并没有变得像以后那样重要。
除了语言本身的问题,B编译器的threaded代码技术得到的程序,比他们对应的汇编语言版本慢很多,以至我们对用B纪录操作系统或它的中心工具的可能性打折扣。
到1971年时,我开始通过添加一个字符类型,并重写它的编译器以生成PDP-11机器指令而非threaded代码,来扩展B语言。
因此从B到C的转换,与创造一个同汇编语言竞争,能产生足够快和小的程序的编译器,是同时进行的。
我称这个轻微扩展的语言为NB,表示“新B”(newB)。
C萌芽
NB只存在了很短时间,以至没有编写一个它的完整描述。
它提供类型int和char,它们的数组,指向它们的指针,用典型风格声明如下Tjy平坦软件园
inti,j;
charc,d;
intiarray[10];
intipoint[];
charcarray[10];
charcpoint[];
数组的语义与在B和BCPL中保持一样:
iarray和carray的声明产生的单元,被动态初始化为分别指向十个整数和字符序列中的第一个的值。
ipointer和cpointer的声明省略了尺寸,以表明没有存储被自动分配。
在过程内部,语言对指针的解释与数组变量是一样的:
一个指针声明产生一个单元与数组声明的区别仅在,程序员被期望给它赋值,而不是让编译器分配空间和初始化单元。
值存储在数组的单元中,指针是按字节计算的,对应存储区的机器地址。
因此通过