📄 标 题 c语言历史zz.txt
字号:
水木社区 → C程序设计语言 → 精华区文章阅读
CProgramming 版 (精华区)
发信人: ccrazy (我为C狂), 信区: CProgramming
标 题: c语言历史zz
发信站: BBS 水木清华站 (Tue Jan 11 23:28:46 2005), 站内
发信人: daizisheng (呆子圣), 信区: AnsiC
标 题: c语言历史
发信站: 瀚海星云 (Thu May 29 17:49:11 2003)
C语言的发展
---Dennis M.Ritchie (就是K&R中的R)
dmr@bell-labs.com
翻译:daizisheng,水平有限,敬请原谅
概述:
70年代初,c语言以系统实现语言(system implementation language)的角色开始
出现在早期的UNIX系统上。它由无类型(typeless)的BCPL语言发展而来,并引入了
丰富的“类型”,而他的产生最初是为了改进小型机(tiny machine)的编程环境。
今天,c语言已经成为了一门极具优势的语言,这篇文章着眼于c语言的整个不断发展,
不断革新的历史过程。
简介:
这篇文章着眼于介绍c语言的发展过程,它的影响,以及它产生的条件。为了简短,
我省略了对c语言本身的详细描述,同样也省略了对其前身B,以及B的前身BCPL语言的详细
描述。取而代之,我们将关注每种语言的主要特征以及他们是怎样变迁的。
c语言在1969-1973这段时间产生了出来。这段时间刚好也是UNIX系统早期发展的时间。
而这几年中最具有创造性的时间出现在1972年。另外一个比较大的改变发生在1977-1979,
这个时候UNIX系统的可移植性已经被证实。在这个时段的中期,第一部关于c语言的详细
描述的书籍问世了,这就是<<The C ProgrammingLanguage>>,这本书通常也被称为"白皮书
"或者"K&R"。最终,在80年代中期,c语言被ANSI X3J11正式标准化。这个标准后来也得到
了修正。直到80年代初期,虽然c语言的编译器出现在了各种不同的体系结构以及操作系统
上,但是它仍然是几乎和UNIX系统捆绑在一起的,直到更晚的一些时候,c语言才扩散到
越来越广泛的体系上。到了今天,c语言已经成为了一门在整个计算机业上的普遍应
用的语言了.
历史:
60年代后期对于贝尔电话实验室是一个混乱的(turbulent)年代。公司被卷入了一个由
MIT,通用电气以及贝尔实验室联合发起的有点探险性质的Multics项目。到了1969年,贝
尔试验的管理层以及研究人员们开始感觉到Multics项目如果要实现,将会拖到很晚,
并且将会付出高昂的代价。就是在GE-645 Multics机被从计划中取消之前,一个由Ken
Thompson私人领导的非正式的团队就开始改变他们的目光了。
Thompson想通过他自己的设计,利用一切可以利用的办法,实现一个比较令人满意的
计算机环境。这个计划包含了许多Multics的创新思想,比如清楚的把一个进程当作一个
控制的轨迹,树型结构的文件系统,一个用户级的命令解释器,文本文件的简单表示,
设备的通用存取。他们也剔除了一些其它的东西,比如统一的内存存取以及文件存取。
最开始,Thompson以及我们其他的人推迟了Multics的另外一个创新特点,也就是以一种
高级语言书写其中的大多部分。对PL/I--Multics的实现语言,我们没有作太多的尝试。
我们同时使用其他的语言,包括BCPL。我们感到很遗憾,这样失去了以汇编语言书
写程序的很多优点,比如书写容易,易于理解。在那个时候,我们并没有很注重可移
植性,直到后来,我们才对这个发生了兴趣。
Thompson当时面对的硬件环境即使在当时也是很难懂,体系很简单的:DEC PDP-7。
1968年他开始的时候是一台拥有8K 18-bit字内存并且没有任何可以使用的软件的机器。
在希望使用高级语言的同时,他使用PDP-7汇编语言写了起初的UNIX系统。在最开始,
他甚至就没有对PDP-7本身写程序,而是在一台GE-635机器上使用一堆GEMAP汇编器的宏。
一个postprocessor(??)将这些生成为PDP-7可读的纸带(paper tape)。
这些纸带知道后来才被放到PDP-7上测试,这个时候一个简单的UNIX内核,一个编辑器
,一个汇编器,一个简单的shell以及一些有用的工具已经完成了。这个时候,整个操作系统
就是自支持的了:程序的书写以及测试再也不需要求助于纸带了,整个系统的开发就可以在
PDP-7系统本身上做了。
Thompson的汇编器在简单性上即使是对DEC的也有过之而无不足,它对表达式进行求
值并改变(emit)相应的位。这个时候没有库,没有装载器,没有连接器,程序的整个源代码
都送给汇编器,并且输出程序有着一个固定的名字以显示它是可以直接执行的。
(这个名字是a.out,这就是解释了现在UNIX操作系统的一个特点:即使在后来系统引入了
连接器以及能够明确指明另外一个名字,但是这个仍然被保留为编译后默认输出的可
执行结果的名字。)
在1969年第一个UNIX系统在PDP-7上开始运行不久,Doug Mcllroy为这个新的系统创
建了一门高级语言:一个McClure的TMG的实现。TMG(TransMoGrifiers)是一种用于书写
编译器的语言。他使用自上而下的递归风格(stytle),并结合了上下文无关文法属性。
Mcllory和Bob Morris就是使用TMG为Multics写了早期的PL/I编译器的.
面对Mcllroy重写TMG功绩的挑战,Thompson决定系统(当时还没有命名)需要一种系
统语言。在快速的尝试Fortran后,它创造了一门他自己的语言,这就是B。B可以被认为
是没有类型的C语言,更准确地说,它就是将BCPL压缩到8k bytes的内存,当然是经过
Thompson自己的改进了的。它的名字很可能就表达了它是BCLP的压缩。另外一些不同
的观点则认为B是从Bon设计而来得,这里的Bon是Thompson在做Multics项目的时候设
计的一门语言,而Bon这个名字这是由他的夫人Bonnie而来得,不过有一份手册表明
Bon来自于一个宗教,在这个教里面有低语的宗教规则。
起源
(这篇涉及到BCPL和B中的很多术语,有的地方真的搞不懂)
BCPL由Martin Richards于60年代中期,也就是他正visit MIT的时候,设计
出来,并在70年代早期广泛的用于很多的项目当中,这些项目包括Oxford的OS6
操作系统,Xerox PARC的早期工作(seminal Alto work)。我们后来熟悉它,完
全是归功于MIT CTSS系统,Richards自己就是使用这个系统作Multics开发的。
早期的BCPL汇编器被移植到了Multics以及GE-635 GECOS系统上,这些工作是由
Rudd Canaday完成的,而Bell实验室则将它移植到了更多的其他系统上。还在
Multics在Bell实验室的最后的一段时间以及不久以后,它便成为了这群不久就
卷入了UNIX的人们的选择。
BCPL,B,C和Fortran以及Algol 60一样都属于过程性语言。相比之下,他们
面向系统程序设计,精小,很容易被描述,并且很容易被简单的编译器编译。
他们更加靠近机器本身,因为他们所引入的抽象很容易基于传统计算机系统所
提供的数据类型以及操作,同时他们依靠库例程进行基本的输入输出以及其他
和操作系统的交互。他们也使用库例程做一些其它的控制结构,比如协同过程,
过程结束等等。同时,他们的抽象都处于一个充分高的层次,在各个可以使用
的系统之间具有可移植性。
BCPL,B,C在语法上有很多的细节不同,但是在很多的方面他们是类似的。
程序由全局数据的声明和函数(过程)的声明序列构成。在BCPL中,过程是可以
嵌套的,但是不可以使用包含过程的过程的非静态声明的数据。B,C则使用更加
严格的做法从而抛弃了这一限制:在B和C中不允许使用这一嵌套结构。这些语言
都支持分开编译,(除了早期的B外),并且提供了一种办法通过名字将其它的
文件包含进程序里面。
BCPL中的许多的语法词法机制和B和C相比起来更加的文雅和正规。例如,
BCPL的过程和数据声明有一个更加正规的结构,并且它提供了一个更加完整的
循环结构集合。虽然,BCPL程序概念上是一个无界的字符流,但是聪明的规则
使的大多数紧跟在行边界语句后的分号被忽略掉(??)。B,C则没有使用这个做法,
它们使用分号来结束大多数语句。尽管有很多的不同,大多数的BCPL的操作和
表达都在B和C语言中都有相应的对应。
BCPL和B的一些结构上的差异主要在于中间内存(internediate memory)的限制。
例如,BCPL的声明可以使用这种形式:
let P1 be command
and P2 be command
and P3 be command
在这里command所代表的程序段包含了整个过程。而有and所引出的声明,比如上
面的P3,被认为是P1的内部过程声明。类似的,BCPL能够将一组声明包装在一个
有值的表达式里面,例如:
E1 := valof $( declarations ; commands ; results E2 $) + 1
BCPL的编译器能够通过输出前在内存中存储和分析整个程序来处理这些结构。由于
存储空间的限制,B语言中要求一种one-pass技术来尽快的输出结果,而由此而来
的语法上的重新设计也被带入了C语言中。
BCPL的另外一些不尽人意的方面也是技术问题,这些在B语言的设计的时候被剔除
掉了。举一个例子,BCPL使用一种“全局表”的机制来处理分开编译的程序之间
的交互。在这种机制下面,程序员必须明确的将程序的任何外部可见的数据和过程
和这个表的一个偏移量联系在了一起,而连接器则使用这些偏移量来完成连接过
程。B语言在最开始的时候主张整个程序应该以一个整体提交给编译器,从而避开了
这种机制。而后的B以及所有的C语言的实现则使用了连接器来解决这些分开编译的
程序的外部符号的问题,从而把这些繁复的给名字分配偏移量的工作从程序员手中
接了过来。
其它的一些从BCPL到B的改变是作为一种尝试引进来得,其中的一些仍然是有争议
的。比如B中使用=号作为代替原来的:=的赋值符号。类似的比如B使用/**/来包含
注释,而在BCPL中则是使用//把紧跟在后面的直到行的结束作为注释,PL/I就继承了
BCPL的这一点,这里也可以看到C++也回到了BCPL的这种注释机制上来了。同时,
Fortran也影响了B的声明语法:B的声明以specifier比如auto,static等开始,紧接
着是一个名字串,而在C中不仅仅跟随了这种风格,而且还引入了类型关键字放在
声明的开始部分。
并不是所有从BCPL来得改变在Richard的书中都记录了下来,B是经过深思熟虑的。
我们使用的是BCPL的一个比较早的版本。例如,用于switchon结构退出的endcase在
我们60年代使用的版本就没有。所以使用break关键字来退出B和C的switch结构归咎
于分支结构的发展而不是从BCPL发展而来的。
与B的设计的时候从BCPL来得语法变迁相比,BCPL的核心--它的类型的结构以及它
的表达式赋值规则--在B中都保留了下来。它们都是无类型的,或者说就只有一种数
据类型"word"或者"cell",这些都是固定bit长度的。这些语言中的内存都是这些基本
单元的线性数组,而这些基本单元中的内容取决于使用的操作。以+操作为例,简单
的使用机器整数加指令来完成操作数的相加,其他的算术操作则同样对操作数的实际
意义并不在意。因为内存是一个线性数组,所以使用一个索引来引用内存中的一个基
本单元便成为了可能,并且在BCPL中提供了一个操作符号来实现这种想法。在最开始,
它使用rv,接着是!,而在B中则是使用*。因此,如果p是一个单元的索引,*p则就是所
指向单元的实际的值,这个既可以作为表达式中的一个值,也可以作为赋值的对象。
因为BCPL和B中的指针就是一个代表内存数组索引的整数,所以在它上面的算术操作
就是有意义的。这种约定是这些语言中的数组语法的基础。在BCPL中的
let V = vec 10
和B中的
auto V[10];
的效果都是一样的:一个名字被称为V的内存单元被分配,并且有一个10长的连续内存
单元序列被分配到其旁边,这个序列的第一个单元的内存索引被放在V中。在通常的
规则下,B中的表达式:
*(V+i)
把V和i相加来引用V后面的第i个单元。在BCPL以及B中都增加了另外的语法来方便这种
内存的存取;在B中上面的表达式等价于
V[i]
而在BCPL中则是
V!i
即使在在当时数组的这种处理方法也不是很通用的,而C则以更加方便的做法吸收
了这一做法。
BCPL,B,C没有哪一个对字符串有比较强的支持,他们都把字符串当作整数向量(
vectors of integers)并且提供了一些通用的规则来提供一些使用上的便利。在BCPL
和B中一个字符串代表的是一个地址,这个地址指向一个静态区域,而这个静态区域被
初始化为了这个字符串中的所有字符。在BCPL中这个区域的第一个字节代表字符串的
长度;在B中则没有这个长度字节,取而代之的是一个终结符,B中称之为‘*e’。这
种改变一方面是用来摆脱一个8-bit 或者是9-bit字节的长度字节所带来的字符串长度
的限制,另一方面因为在我们经验中保存长度的办法相比之下没有使用终结符来结束
字符串的办法方便。
BCPL中单个的字符经常是通过把字符串扩展到另外的数组里面来存取的,这种扩展
将每个字符映射到一个内存单元里面,并在稍后进行重新的打包压缩;B提供了
相应的处理过程,但是人们通常使用另外的库例程来存取或替换字符串里面的单个字
符。
更多的历史:
当B的TMG版本开始运行后,Thompson使用B重写了B本身。在开发过程中,
他继续和有限的内存容量作较量:每次语言的扩充都会使编译器变大,而
每次重写编译器又会缩小编译器。例如,B引入了一类通用的赋值操作符,像
使用x=+y来在x上增加y。这个想法来自经由Mcllroy的Algol 68,Mcllroy就
把这个结合到了他自己的TMG版本当中。(在B以及早期的C中,这些操作符
使用=+,而不是现在的+=,这个“错误”在1976年被修改掉,而这种做法则是
因为当时的B词法分析器拥有一种吸引人的简单的处理方法。)
Thompson更进一步引进了++,--操作符来对操作数进行加一或减一的操
作,并由他们出现的位置是前缀还是后缀来决定改变操作数的时机--使用
操作数之前还是之后。他们并不是一开始就出现在最早的B中,而是在后面
在逐渐发展起来的。人们都猜测当初引入他们是为了利用使得C以及UNIX变
得流行的PDP-11体系所提供的自动增减的地址模式(auto-increment and
auto-decrement memory modes)。但是这种猜测在历史上是错误的,因为
B语言设计出来的时候根本就没有PDP-11。在PDP-7上倒是有一些自动增的
内存单元,这些单元在通过他们的内存间接引用的时候自动增加其值。这
种性质也许为当初Thompson的这些操作符的创建提供了灵感,但是这些操作
符的可以前缀也可以后缀的通用性则是属于他自己的想法。事实上,这些
自动增加的内存单元并没有在这些操作符的实现中直接用到,一种更强烈
的促使这种创新的原因或许是他意识到++x比x=x+1更加的短小。
PDP-7上的B编译器并不生成机器指令,而是生成“threaded code”。
这是一种解释型的代码,是编译器输出的一串由基本操作代码片断,而这些
操作则是运行在一个简单的栈式机上的。
当时的在PDP-7上的UNIX系统只有很少的包括B本身在内的部分是用B写
的,因为当时的机器很小很慢,除了试验以外什么也不能够作。使用B来重
写整个操作系统以及其他系统部分代价将会十分的昂贵,并且看起来也是不
可行的。在某一点上,Thompson通过提供一个“虚拟B”编译器来克服地址空
间的限制。他通过使用解释器分页程序的代码和数据来允许被解释的程序
占用多余8K的内存空间。但是这种机制太慢了,不能够广泛的用于实际的
应用中。不过,还是有一些利用B写的应用出现在那个时候,比如为UNIX用户
所熟悉的可变精度计算器dc的早期版本。我所处理到的最具雄心的计划是
一个实在的将B语言转换成GE-635机器指令而不是前面的的“threaded code”
的交叉编译器。这是一个小型的“tour de force”:一个完整的B编译器,
他用他自己的语言写成,声称36位的大型机的代码,并且运行在一个18位
的并且只有4k用户地址空间的机器上。这个项目之所以可能就是归功于B
语言的简单以及他的运行系统。
虽然我们偶然有实现当时的主流语言比如Fortran,PL/I,Algol 68,
但这看起来就我们的资源来说是无望的:我们只有十分简单的小工具可以
利用。所有的这些语言都影响着我们的工作,但是我们倒觉得使用我们
自己的东西更有趣。
到1970年,UNIX项目已经展示出了足够的潜力,我们也能够获得新的
DEC PDP-11了。处理器是DEC运来的第一批产品之一,3个月后他的磁盘也
运来了。为了使B程序的“threaded code”能够在上面运行,我们只是需要
为那些操作写对应的代码片断以及一个后来由我完成的汇编器。不久,dc
成为了在PDP-11上运行的第一个比较有趣的程序,而在这时,机器上还没
有任何的操作系统。很快的,还在磁盘运抵之前Thompson就已经将UNIX内
核以及一些基本的命令使用PDP-11的汇编语言重新编码。在机器上的24K
字节的内存中,早期的PDP-11操作系统使用12K来供操作系统使用,另外
把一段很小的空间供用户程序适用,而剩下的作为RAM disk。这个系统
只用来做测试使用,并不是为了做什么实际的工作;机器通过枚举可变
棋盘上的闭合骑士行迹来测试时间。当磁盘出现后,我们搞懂了PDP-11的
汇编语言后就迅速的转向了他,并把B中已经有的东西移植了过去。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -