这一章讲的是C语言的发展史,包括它是多么不经意的诞生,而后又经历了早期C、K&C、ANSI C的各种阶段,直到它现在形成的这个样子。C语言从来不是一门完美的语言,所以它一直在发展,直到今日,它仍然有很多不足之处,但这些都掩盖不了它的光辉。
C语言的产生源于一个失败的项目,这大概令许多人大吃一惊。这个项目是通用电气、麻省理工以及贝尔实验室联合创立的,Multics工程的目的是创建一个操作系统,结果当然就是操作系统失败了,C语言却从中诞生了。这个诞生的过程并不复杂,这个项目失败后,其中一个叫做Ken Thompson的研究人员用汇编语言为PDP-7编写了一个操作系统(这个PDP呢,相信看过《黑客——计算机革命的英雄》这本书的朋友不会太陌生,它是第一台小型机产品,PDP系列的计算机是早期麻省理工大学那块区域的黑客们爱如珍宝的计算机),这个操作系统就是有名的UNIX。Thompson发现用汇编语言编写UNIX显得非常笨拙,于是想利用高级语言的一些优点来完成它,在用Fortran进行了一番简短而又不成功的尝试后,他把用于研究的语言BCPL作了一番简化之后,就创建了B语言。当1970年开发平台转移到PDP-11之后,B语言的某些特性就变得有些不合时宜了。在这里就顺便说一下B语言的一些特性,它从BCPL语言简化而来的一个比较好的想法就是“引用数组元素相当于对指针加上偏移量”,现在的C语言同样沿用这个想法,它跟C语言一个比较大的区别就是它是没有类型的,这个问题导致它在PDP-11中显得有些不合时宜,因为PDP-11机器具有不同数据类型。Thompson的同事Dennis Ritchie利用PDP-11强大的性能,创立了能同时解决多种数据类型和效率的NEW B语言(这个名字很快变成了C语言),它与B语言的另一个重大区别就在于C语言采用编译模式而不是解释模式。至此,C语言诞生了。
接下来谈谈早期的C语言,早期的C语言的许多特性是为了方便编译器设计者而建立的(当然,并不是为程序员写的),由于是为了编译器设计者而建立的,就导致了C语言简化的程度比较大,回避了一些复杂的语言要素(但是好像在C++中又出现了),这些特性让C语言更容易学习和实现,并且高效。早期的C语言并没有定义I/O,而是由库函数提供,可移植的I/O由Mike Lesk编写,最早出现于1972年左右,可在当时的三个平台上运行,但它的性能低于预期值,所以,经过人们对它的优化和裁剪,后来成为了标准I/O库函数。大约在这个时候,预处理器也加入进来,由于预处理器的强大替换功能,甚至可以改变C语言的语言结构,所以一般来说,不太提倡过度地使用它。
至20世纪70年代中期,C语言已经很接近我们现在所使用的样子了,不过还是有许多区别。1978年,C语言经典名著The C Program Language出版的时候,也就是“K&R C"开始出现,由于这本书的影响力之广,导致K&R C广泛的传播,时至今日,这本书仍然是经典之作,不得不说,这本书也大大影响了C语言,使之成为当时最成功的编程语言之一。所以,想要学习C语言,The C Program Language确实是一本值得推荐的书,唯一要注意的就是它是K&R格式,当然,实际也相差不了太多。
在20世纪80年代初,C语言虽然被广泛使用,但是由于编译器的不同,出现了许多版本,这可不是一个好的苗头,C语言受到了可能变成一种多个变种松散相关的语言的威胁,不过幸运的是,当时有许多其他语言都有自己正式的语言标准,所以C语言也走向这条路。K&R C对于C语言做了近40页的工作报告,1983年ANSI也成立了C语言工作小组,开始了C语言的标准化工作,最终在1991年3月发布了ANSI C语言标准,ANSI C标准是我们现在所使用C语言的公认标准,现在的绝大多数编译器都支持ANSI C标准,关于ANSI C与K&R C的不同之处可以浏览书上相关解释或有关资料。
可以明确的是,ANSI C标准制定之后,代码的可移植性也相应的变强,只要遵守标准,能保证你的代码绝大多数情况下在多个平台下都能产生一样的结果,当然在你没有编写依赖编译器的代码的情况下,不过在ANSI C中有一条有趣的规定——编译器只有在违反语法规则和约束条件的情况下才能产生错误信息,这意味着所有不属于约束条件的语义你都可以不遵循,而且由于这种情况是未定义的,编译器可以采取任何行动,甚至不通知你。这个规则使得程序员在很多情况下意识到自己的错误变得更加困难,因为编译器可以不产生错误信息来提示你,除了语法错误和约束规定。
从C的整个发展来看,它的形成过程比较曲折,有许多不经意,也有许多必然因素在里面,ANSI C标准的制定,使得我们有规则可循,当我们对某个用法不能确定或者对某个错误摸不着头脑时,可以回过头去查询标准规定,这无疑是个不错的做法,不过,这种做法的缺点也显而易见,一种东西一旦拥有所谓的标准,对它的修改、校正也会变得麻烦和形式起来,不过总得来说,这种标准的规定还是很有意义的,这也是C语言广泛应用的重要原因之一。另外一方面,我们可以发现尽管有了标准,但是编译器还是拥有很大的权力来对程序的编译进行处理,各种不同的编译器的编译过程也各不相同,由于这一点,程序的可移植性会有所降低,所以这个重大的责任就落到程序员的身上,尽量少编写一些依赖于编译器的程序。