计算机专业必须知道的东西——C语言的发展

计算机专业必须知道的东西——C语言的发展

概要

在1970s早期,C编程语言是作为新生的Unix操作系统的系统实现语言而设计的。衍生于无类型(typeless)语言BCPL,它进化出了一个类型结构【类型系统】;在弱小的机器上,作为改善简陋编程环境工具的这一创新,它已然成为当今主流语言之一。本文研究它的进化。
NOTE: *Copyright 1993 Association for Computing Machinery, Inc. This electronic reprint made available by the author as a courtesy. For further publication rights contact ACM or the author. This article was presented at Second History of Programming Languages conference, Cambridge, Mass., April, 1993.
It was then collected in the conference proceedings: History of Programming Languages-II ed. Thomas J. Bergin, Jr. and Richard G. Gibson, Jr. ACM Press (New York) and Addison-Wesley (Reading, Mass), 1996; ISBN 0-201-89502-1.

历史背景

1960s晚期,是Bell Telephone Laboratories(贝尔电话实验室)的计算机系统研究(中心)的动荡岁月[Ritchie 78] [Ritchie 84]。计算机被从Multics项目组拖走[Organick 78],该项目是MIT(麻省理工学院)、General Electric(通用电气公司)和贝尔实验室的合作项目。到1969年,贝尔实验室管理层、甚至研究人员都认为,Multics项目不能按期完成并且代价高昂。甚至在GE-645 Multics机器被撤走之前,一个非正式小组——早期由Ken Thompson领导,已经着手一些其它的研究。
Thompson希望按照自己的设计、使用可用的任何方式,创造一个舒适的计算环境。事后诸葛亮地说,他的计划集成了Multics的许多创新方面,包括关于进程的清晰概念——控制块,树型结构的文件系统、作为用户级程序的命令解释器、文本文件的简单表示和访问设备的通用化。他们排除了其余特性,比如对内存和文件的统一访问。此外,开始的时候,他与我们这些俗人遵循[推迟?]着Multics的另一个先驱性(虽然不是原创)的特性,即几乎仅用高级语言来编程。PL/I【Programming Language One,IBM公司在1950s发明的高级编程语言】——Multics的实现语言,不太符合我们的口味,因而我们也使用其他语言,包括BCPL,我们担心【regretted?】失去使用在汇编程序的级别以上的语言进行编程的优势,即容易编写、易于理解。当时我们并未特别关注可移植性,到后来才有了这方面的兴趣。
Thompson面临的硬件环境,即使在那个时代也是又拥挤又简陋:他从1968年就开始使用的DEC PDP-7,只有8K的18bit字(长)的内存,并且没有对他有用的软件。虽然心想着使用高级语言,他还是用PDP-7汇编器编写了最初的Unix系统。刚开始的时候,他甚至并未在PDP-7上编程,而是在一台GE-635机器上使用GEMAP汇编器的一些宏。一个后处理器(postprocessor)生成PDP-7可读的纸带。
这些纸带从GE机器拿到PDP-7上进行测试,直到一个原始的Unix内核(kernel)、一个编辑器、汇编器、一个简单的外壳(shell)(命令解释器)和一些工具(像Unix rm, cat, cp命令)被完成。此后,这个操作系统可以自我支持:可以编写和测试程序,勿需借助纸带,并且程序开发可以在PDP-7上持续进行。
Thompson的PDP-7汇编器在简明性上甚至优于DEC的;它对表达式求值并得到对应的比特流【二进制源代码】。没有库、没有装载器或没有链接器:程序的全部源文件提交给汇编器,而其输出文件(the output file)——有一个固定名字——是可以直接执行的(这个名字,a.out,道出了一点Unix渊源;它是汇编器的输出。甚至在系统有了链接器和有了显式指定另一个名字的方式之后,它仍被保留作为编译器的默认可执行文件(名))。
Unix在PDP-7上首次运行后不久,1969年Doug McIlroy创造了这一新系统的第一个高级语言:一个McClure的TMG实现[McClure 65]。TMG是一种自顶而下,递归降解(top-down, recursive-descent)风格的编写编译器(更一般地,TransMoGrifiers)的语言,它将上下文无关的语法表示法与过程(式程序)元素相结合。McIlroy和Bob Morris使用TMG为Multics编写了早期的PL/I编译器。
受McIlroy重造TMG事迹的刺激,Thmopson认为Unix(当时可能还没有取这个名字)需要一种系统编程语言。经过用Fortran的短暂而受阻的尝试后,他创造了一门他自己的语言,他称之为B。B可被视为没有类型的C。更准确地,它是塞进8K字节内存,经过Thompson大脑过滤后的BCPL。它的名字最有可能表示为BCPL的缩写,尽管另一种理论认为它源自于Bon[Thompson 69],Thompson在Multics的那些日子创造的一门不相关的语言。Bon进而二中其一,可能是以他妻子Bonnie的名字,或者(根据它的操作手册中的一个百科全书般的引用)以一种宗教命名,该教仪式涉及咕隆咕隆的神奇咒语。
一,与C语言相关的语言很多。其中最早的一门语言叫 Algol 60,是 1960 年产生的,它是真正的第一门面向问题的语言。但是这门语言离硬件比较远,所以 1963 年剑桥大学在 Algol 60 的基础上研发出了 CPL。CPL 同 Algol 60 相比更接近硬件一些,但规模比较大,难以实现。1967 年剑桥大学的马丁·理查兹(Martin Richards)对 CPL 进行了简化,产生了 BCPL。BCPL 中的 B 就是 Basic 的缩写,即“简化的”。

二,1970 年,美国 AT&T 公司贝尔实验室(AT&T Bell Laboratory)的研究员肯·汤普森(Ken Thompson)以 BCPL 为基础,设计出了很简单而且很接近硬件的B语言(取 BCPL 的首字母)。B语言是贝尔实验室开发的一种通用程序设计语言。虽然它没有流行起来,但是它很重要。肯·汤普森用B语言做了一件很重要的事情,一直影响至今,即他用B语言写出了世界上第一个操作系统——UNIX 操作系统。

三,1971 年,贝尔实验室的丹尼斯·里奇(Dennis Ritchie)加入了肯·汤普森的开发项目,合作开发 UNIX。他的主要工作是改造B语言,使其更加成熟。

四,1972 年,丹尼斯·里奇在B语言的基础上最终设计出了一种新的语言,他以 BCPL 的第二个字母作为这种语言的名字,即C语言。

五,1973 年年初,C语言的主体完成。肯·汤普森和丹尼斯·里奇开始用C语言完全重写 UNIX,这就是 UNIX 第 5 版。随着 UNIX 的发展,C语言自身也在不断地完善。直到今天,各种版本的 UNIX 内核和周边工具仍然使用C语言作为其最主要的开发语言,其中还有不少继承肯·汤普森和丹尼斯·里奇之手的代码。

六,UNIX 系统是世界上第一个真正的操作系统。由于 UNIX 操作系统是用C语言编写的,而这个系统很流行,于是C语言也跟着流行起来。而 UNIX 操作系统是开源的,所以别人要想学习,就要先学C语言。

七,B语言被C语言改写后,C语言流行了而B语言就被淘汰了。而且后来发现,C语言的确非常好,它是面向过程语言的代表,是有史以来最重要的一门计算机语言。

八,随后又出现了 C++。C++ 是本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)编写的,他也来自贝尔实验室,是C语言创始人丹尼斯·里奇的下属。C++ 就是在C语言的基础上发明的。C++ 进一步扩充和完善了C语言,是一种面向对象的程序设计语言。

后来 Sun 公司又对 C++ 进行改写,产生了 Java。而微软公司发现 Java 很流行,就造出了一个类似的语言——C#。所以 Java 和 C# 都源自于 C++。

以上就是C语言演变的过程。从这个过程我们可以看出,如果以后要学习 C++、Java 或者 C# 的话,那么C语言就必须要学!因为它们都源自于C语言。而且C语言中绝大部分的知识,在 C++、Java、C# 中几乎都会用到。C语言里面有两个知识点是必须要学的,一个是函数,另一个是指针。这两个知识点是整个C语言的主体和核心。而且这两个知识点在其他语言中是学不到的,或者是同C语言中有差别。总之,C语言是它们的“老祖宗”,学习其他语言之前最好要将C语言学好。

起源:这些语言

BCPL是由Martin Richards于1960年代中期,在访问麻省理工学院时设计的;在1970年代早期它被用在几个有趣的项目中,其中包括位于牛津的OS6操作系统[Stoy 72],和施乐公司PARC研究中心的创造性项目Alto的一部分[Thacker 79]。我们熟悉该语言,是因为Richards工作过的MIT CTSS系统[Corbato 62]被用于Multics开发。最初的BCPL编译器被Rudd Canaday和贝尔实验室的一些人们移植到Multics和GE-635 GECOS系统上[Canaday 69];在贝尔实验室的Multics项目奄奄一息的阶段,它成为随后转入Unix的一帮人选择的语言。
BCPL、B和C都归属于以Fortran和Algol 60为代表的传统过程式家族。它们格外地倾向面向系统编程、小巧、描述简洁,而且可被简单的编译器轻易地翻译。它们接近机器,它们所引入的抽象以传统计算机所提供的具体数据类型和操作为基础,它们依赖于例程库以输入输出以及与操作系统的其它交互。尽管不太成功,它们还使用库程序指定其他有趣的控制结构,如协程和过程关闭。同时,它们的抽象层次足够高,足够用心的话,能达到机器间的可移植性。
BCPL, B和C在很多细节上存在语法差异,但总体上它们是相似的。程序由一系列的全局声明和函数(过程)声明组成。BCPL中,过程能够嵌套,但不能引用定义在外包过程中的非静态对象。B和C通过更强行的限制避免了它:基本就没有嵌套过程。每一种语言(除了早期的B版本)都认可(文件的)分别编译,并提供了从指定文件中包含(including)文本的方式。
BCPL中的若干语法和词法机制较B和C中的更优雅和正式。例如,BCPL的过程和数据声明拥有更一致的结构,并且它提供了一套更完整的循环结构。尽管BCPL程序名义上是由分界的字符流构成,聪明的规则使得以每一行结束的语句可以省略其分号。B和C忽视了这种便利,大多数语句以分号来结束。刨除这些差异,BCPL的大多数语句和操作符直接对应B和C中的相应物。
BCPL和B之间的一些结构化的差异源于介质存储的限制。比如,BCPL声明采用这样的形式
let P1 be command
and P2 be command
and P3 be command
 ...

此处的由命令表示的程序文本包含完整过程。这些子声明相互关联而且同时出现,所以名字P3在过程P1内可见。相似地,BCPL能在求得一个值的表达式里包含一组声明和语句,例如
E1 := valof ( declarations ; commands ; resultis E2 ) + 1

BCPL编译器在产生输出前,通过存储和分析内存中该完整程序的解析过的表示,可以容易地处理此类构造。B编译器所受的存储限制决定了一种一次通过( one-pass)技术,由此尽可能快生成输出,而这一语法上的重新设计将这种可能带入到C。
BCPL中少量的、归结于技术问题的讨嫌方面,在B的设计中被有意地避免了。例如,BCPL使用一个“全局向量”(global vector)机制用于在分别编译的程序之间通信。在这种模式下、程序员要显式地将每一个外部可见的过程和数据对象的名字,与全局向量中的一个数值偏移量关联起来;被编译的代码通过使用这些数值偏移量完成链接。B避免了这种麻烦,起初是将整个程序一次性地全部提交给编译器。B的后期实现——C也如此,使用一个传统的链接器来解析分别编译的文件中的外部名字,而不是把指定偏移量的负担推给程序员。
从BCPL到B迁移的某些搞法,源于偏好而且存在争议。例如使用单个=字符代替:=表示赋值的决定。还有,B使用/**/括起注释,而BCPL使用//以注释掉直至行末的文本。这显然是PL/I的传统。(C++重新启用了BCPL的注释方式)【C语言也启用了//注释】Fortran影响了声明的语法:B的声明语句先是一个auto或static这种修饰符,后面是名字列表;C不仅遵循这种风格,还把类型关键字放在声明语句的最前面。
并非Richards的书[Richards 79]规范的BCPL与B的每一个不同都是有意的。我们起步于BCPL的一个早期版本[Richards 67]。例如,在我们于1960年代开始学习它时,从 BCPL的switchon语句跳出的endcase并没有出现,因而B和C中都使用的跳离switch语句的关键字break,只能说是殊路同归而非有意的改变。

相对于创建B的过程中产生的诸多语法变化,BCPL的核心语义内容——其类型结构和表达式求值规则——保持不变。两者都是无类型的(typeless),或者说只有单一的数据类型,“字”(word)或“单元”(cell)——一个固定长度的位模式【只有机器字】。这些语言中,内存由这些cell线性数组构成,每一个单元【保存】的内容的含义由所用的操作决定。例如+操作符,使用机器的整数加法指令,将两个操作数简单相加,其它的算术运算也同样不管它们的操作数的实际含义【操作数就是一个01串,不管它是int、char还是位图数据】。由于内存是一个线性数组,就可以将单元的值解释为该数组的索引,而且BCPL为此提供了一个操作符。开始被拼作rv,后为!,而B使用一元*。因此,如果p是这样的单元,保存另一个单元的索引(或地址,或指向它的指针),则*p表示被指向单元的内容,不管*p是作为表达式的值【右值】还是赋值语句的目标【左值】。
因为BCPL和B中指针不过是内存数组的整数索引,对它们进行算术运算是有意义的:如果p是一个单元的地址,那么p+1是下一个单元的地址。这种约定是两种语言中数组的语义的依据。用BCPL编写
let V = vec 10

或用B,
auto V[10];
效果是一样的:命名为V的单元被分配,然后再拔出另一组10个连续单元,而它们中第一个的内存索引被存放在V中。【这不是C的搞法】按照一般规则,B中的表达式 *(V+i)把V和i相加,并指向V后第i个位置。BCPL和B都增加了特别的符号以简化这种对数组的访问;在B中的等价表达式是
V[i]
在BCPL中是
V!i
这种引用数组的方法甚至在当时也不常见;后来C以稍微不同的方式沿用了这一方式。

BCPL,B或C都没有在语言中对字符数据提供强烈的支持;它们都把字符串当作整型的向量[数组]处理,并通过几个约定补充[数组的]通用规则。在BCPL和B中,字符串字面值/文字表示由字符串的字符所初始化的一个静态区的地址,(这些字符)被塞入那些内存单元。BCPL中,第一个被塞的字节包含串所拥有的字符个数;而B,没有该计数而字符串终结于一个特别的字符,在B中被拼写为 `*e’。作出这一改变的部分原因,是避免用一个8位或9位槽(slot)保存串的计数会导致的字符串长度的限制,部分原因是维护该计数似乎,按我们的经验,不如使用一个终结符方便。

BCPL串中单个的字符通常被如此处理:字符串被展开为另一个数组,每单元一个字符,然后再次打包;B提供了相应的例程,但是人们更多地使用其他的库函数以访问和替换一个串中的单个的字符。

Guess you like

Origin blog.csdn.net/qq_48164590/article/details/115153041