C语言是如何被开发出来的

已收录   阅读次数: 1,477
2020-09-0707:23:29 发表评论
摘要

本篇文章介绍了C语言的起源与延伸,读起来浅显易懂

分享至:
C语言是如何被开发出来的

内容详情

C语言的由来之前,我们先来讨论文化演化论。英国人类学之父泰罗(Edward Burnett Tylor)爵士提出「文化演化论」,指一种表示时间形式的过程,持续而且通常会形成文化的累积与进步。在1881年出版的《人类学》(Anthropology)一书中,他认为似乎在任何地方都可发现精致的艺术品、深奥的知识及复杂的典章制度等,都是从较早、较简单、且较粗浅的阶段逐渐发展或进化而来。文化演化论认为文化的进化有下列两种模式:

  • 1.单一路线的进化:把人类文化视作单一的整体,即将整体的任何部分作为一个单位或体系而追溯其进化的历程
  • 2.多条路线的进化:把人类文化看作人为领域的范围,如区域文化或民族文化,从而阐明某些文化及促进其进化的各种因素的交互作用,寻出其进化路线

尽管电脑程式语言的历史相对很短,但若用上述文化演化论的观点来解析程式语言发展仍相当贴切。

符合现代高阶语言特性第一个程式语言是Plankalkul,后者是德国工程师暨电子计算机先驱Konrad Zuse (以设计出Z3可程式化的电子计算机而闻名,曾为了融资而在二战期间和纳粹德国合作)在1943到1945年间所发展,比1954年开始发展的FORTRAN程式语言早十年,但Plankalkul的设计直到1972年才被正式发表(多少与纳粹德国的处境有关),而到第一个用于Plankalkul 语言的编译器着手实作,已是1975年的事情。

因此,如果谈论完整开发并能运作的高阶程式语言,FORTRAN最古老的资格仍无人够撼动。显然Plankalkul几乎掩没于时代的长河,但具备了数值(assignment)、子程式(subroutine)、条件判断(conditional)、浮点数运算、阵列(array)、阶层方式结构、断言(assertion)、例外处理等等特征,甚至FORTRAN还不见得全部拥有。

FORTRAN和Plankalkul不存在相互影响的关系,但光看设计概念的话,却可说趋同演化。用文化演化论的说法,这是多条路线的进化。

C语言至今将近有50年历史,但以程式语言演化的历程来说,仍算是较晚才提出,要论资历,绝对要谈LGOL 60。ALGOL 是Algorithmic Language (演算法所用的语言)的缩写,提出巢状(nested)结构和一系列程式流程控制,今日我们熟知的if-else语法,就在ALGOL 60出现。ALGOL 60和COBOL程式语言并列史上最早工业标准化的程式语言。

1950 年代末期,为满足人们驾驭电脑所需,多种程式语言被提出,但这些程式语言往往围绕在UNIVAC电脑或IBM 700系列大型主机上,要跨越机器间交换或分程式(注意,当时尚未有"software"的概念)相当困难,无形中限制工业发展。为此,1957 年5 月10 日国际电脑协会(Association for Computing Machinery;ACM)受理「研究与开发适用于电脑主机无关的科学应用程式语言」的要求,并指派隔年邀请相关学者专家在瑞士苏黎世举办首度程式语言发展会议,ALGOL是这背景的产物,最初的版本叫做ALGOL 58,源自于1958 年12 月的ALGOL 报告,这启发颇多学术和工业圈的程式语言发展,包含广泛用于美国军事系统的JOVIAL ,后者广泛用于B-52 同温层堡垒轰炸机、F-15 鹰式战斗机、F-16 战隼战斗机等等。

值得一提的是,电脑科学系学生接触到的Backus normal form (BNF) 就是John Backus为了ALGOL 58特别发展,后来BNF在Peter Naur教授的改善后,成为Backus-Naur form,后者的命名,来自知名电脑科学家Donald Knuth教授的建议。

ALGOL 60是欧洲和美国境内科学家的共同创作,在1960年1月11日到16日之间,以下出席者:

  • 欧洲:Friedrich L. Bauer (慕尼黑工业大学,在北大西洋公约组织[NATO] 1967年的会议上,正式探讨'software engineering'一词,界定今日我们所见软体工程的议题), Peter Naur, Heinz Rutishauser (瑞士数学家,程式语言常见的for回圈就是他提出的,最初以德语介词fur,后来改写为英语for), Klaus Samelson (在ALGOL 58/60扮演关键角色,程式的区块结构和透过堆叠来处理执行资讯的手法,就由他提出), Bernard Vauquois, Adriaan van Wijngaarden (Edsger W. Dijkstra的博士班指导教授,两人并肩为荷兰首款电脑ARRA投入软体开发。W-grammar也是Wijngaarden提出), Michael Woodger (英国国家物理实验室[NPL]的电脑科学家,曾和Alan Turing合作开发Pilot ACE电脑,也是Ada程式语言共同设计者);
  • 美国:John W. Backus, Julien Green, Charles Katz (和Grace Hopper准将合UNIVAC电脑上发展程式语言), John McCarthy (人工智慧领域的开创者,在1958年提出LISP程式语言,和Dennis Ritchie同样在2011年过世), Alan J. Perlis(ACM前主席,对编译器设计有重大影响), Joseph Henry Wegstein (投入COBOL程式语言的发展)

这华丽的出席者清单中,许多人是Turing Award (图灵奖,自1966年设立)得主,源自ALGOL的创新尚有1966年Niklaus Wirth与CAR Hoare以ALGOL X为基础,提出改善的ALGOL W,而Niklaus Wirth的研究成果最后导至他在1970年创造Pascal程式语言。经典的Hello World程式在ALGOL 60长得像这样:

 BEGIN
      FILE F(KIND=REMOTE);
      WRITE(F, <"HELLO WORLD!">);
    END.

可以留意到,ALGOL 60在语言层面没有内建的I/O处理机制,所以需要由编译器和执行环境提供对应的I/O处理常式,这意味着没有放诸四海皆准的Hello World通用程式。有没有发现这样的限制和C语言很像呢?

除了ALGOL 60,直接影响C语言的程式语言是BCPL,后者是剑桥大学的Martin Richards于1967年访问麻省理工学院时期所设计。BCPL程式语言的提出,是回应同样是剑桥大学发展的CPL (最初的意思是Cambridge Programming Language,后来由于和伦敦大学合作,更名为"Combined Programming Language",也被戏称为"Cambridge Plus London")。首篇提及CPL的论文发表于1963年,其设计接近硬体,程式设计的门槛高,而且不容易实作其编译器,拖到1970年才有首个CPL的编译器。

BCPL对CPL做了简化,这个"B"字母就是"Basic"的意思,由于BCPL语言设计的精简,所以其编译器可在16 KB的空间实作出来,也是最早Hello World程式出现的程式语言。最初的BCPL编译器被Rudd Canaday博士(贡献早期UNIX档案系统的实作,在Bell Labs退休后,创办三间公司,而且在2015年加入矽谷的新创公司Entefy,还用Ruby on Rails撰写程式,真是活到老、学到老的典范!)和Bell Labs的同事移植到Multics作业系统和GE-635主机中的GECOS作业系统

GE-635是1960年代奇异电气(General Electric; GE)发展的GE-600系列大型主机的一项产品,1960年代,在美苏冷战的时空背景,作为Project MAC的执行者, AT&T, GE及MIT合作开发Multics作业系统。在1960年代初期,MIT设计的CTSS (Compatible Time-Sharing System)已相当成功,因此1964年启动的Multics作业系统专案大幅延伸CTSS的成果,Multics也采用GE-635大型主机。

UNIX作业系统汲取Multics的养分,并且抛开Multics众多笨重且难以在当时中低阶大型主机上实作的特征,诸如动态连结和资料库(史上首个商业关联性资料库就在Multics作业系统上开发)。终身只在Bell Labs工作的Dennis Ritchie在1972年到1973年间发展C语言及其编译器,并在1974年发表经典论文"The UNIXtime-sharing system" ,彼时UNIX作业系统已用C语言重写,并在DEC PDP-11硬体上验证。

Bell Labs的Dennis Ritchie, Ken Thompson, Rudd Canaday等人都在1960年代涉入Multics专案的开发工作,1969年2月Bell Labs因为不满Multics发展牛步化,决定退出和MIT及GE的合作。我们不难发现,原来开发UNIX和C语言的这票Bell Labs 工程人员不仅熟悉Multics,也掌握BCPL 程式语言。

BCPL是首个引入bracket (即{ }和[ ]一类的表示法)的程式语言,还提供单行注解(即// ),BCPL的Hello World程式长得像以下:

 get "streams.d"
    external
    [
    Ws
    ]

    let Main() be
    [
    Ws("Hello World!*N")
    ]

其中开头的get "streams.d"就如同C语言的#include,而external [Ws]宣告外部函式,这里的Ws是write string的意思,再往下就可见到熟悉的Main(),里头的*N是C语言的'\n',即换行符号。

BCPL在1967年发表,而Ken Thompson和Dennis Ritchie在1969年又将BCPL简化,称为B语言,确保得以运作在PDP-7。BCPL, B和C语言都可归类ALGOL 60风格的程式语言,格外适合系统软体的开发,短小精悍且易于撰写对应的编译器,值得注意的是,BCPL的若干语法和语言处理机制比B语言或C语言来得严谨,所以不用仰赖分号(;)来区分个别叙述、资料声明,和副程式。

从上面的BCPL程式可见,BCPL使用global vector机制,让个别编译的单元间,得以存取到副程式和资料,程式设计者需要自行计算偏移量(offset),也就是把重新定位(relocation)的责任交给程式设计师,而后期的B语言和C语言则透过连结器(linker),避开BCPL的global vector使用的不便。

"The Development of the C Language" 一文详尽地介绍BCPL -> B -> New B-> C程式语言间的演化和考量点,强烈建议阅读。

Unix在PDP-7上首次运行后,1969年Doug McIlroy在这之上发展第一个编译器定义工具TMG,得以开发top-down, recursive-descent风格的编译器。同样在BellLabs的Doug McIlroy和Robert (Bob) Morris使用TMG,为Multics开发早期的PL/I编译器。Ken Thompson则开始思考,既然有了TMG,那么UNIX上应该要有专门开发系统软体的程式语言,于是他着手简化BCPL而开发出B语言,并搭配TMG工具来产生早期的编译器。

Ken Thompson尝试用B语言重写B语言编译器(为了self-hosting),PDP-7机器上的B语言编译器不会产生机械码,而是透过一个简单的stack-based虚拟机器来执行转换过的opcode,又因为PDP-7硬体太慢又有严苛的空间限制,所以除了B语言执行环境外,只有很少的程式能用B语言开发。后来Dennis Ritchie出手,改写B语言编译器,允许输出GE-635机械码,也就是成为真正能用的编译器,这也是B语言编译器支援GCOS作业系统的过程。

在1970年,这群Bell Labs的高手终于获得DEC PDP-11主机,这个16-bit处理器对于日后UNIX发展相当重要:他们遇到硬体相容性的议题,萌生再次发展新的程式语言的念头。原本BCPL和B语言都是word-addressed,即无论处理什么资料,都对应到硬体资料汇流排的宽度(即word),但在PDP-11完全不是这回事,PDP-11的设计是byte导向,因此Dennis Ritchie在1971年着手在B语言增添char型态,并改写原本不能产生真正机械码的设计,这样扩充过的B语言被称为"New B",简称NB。

对应的B程式语言手册可参照:

  • "Users' Reference to B":针对16-bit的PDP-11
  • "USERS' REFERENCE TO B ON MH-TSS":针对36-bit的Honeywell 6000系列大型主机New B没有存在多久,很快就被后续的C语言取代,后者重新定义完整的资料类别,在C语言之前的BCPL和B语言都没有型态(type)的观念,但C语言明确规范,却又不会因为类别系统太复杂,导致对应的C编译器不容易实作,这样的设计也反映到指标的变革。

上述程式语言演化的家族史,是为了破除单一路线发展的迷思,也就是说,不存在「机械码-> A语言-> B语言-> C语言」这样的路线,在C语言之前就有好几个高阶语言,甚至比C语言高阶许多,这些程式语言可用来打造各式工具,如组译器(而且要考虑交叉组译的需求,也就是cross-assembler,如上讨论)、原始码对原始码的转换器(pre-processor就是一种类型)、连结器(linker),最后才会是今日我们普遍认定的编译器。

至于如何一步一步建构编译器自身的过程,也就是self-hosting,值得大书特书,编译器专案shecc - Self-Hosting and Educational C Compiler示范如何用寻常的C语言编译器(针对x86_64)产生执行档,接着产生出支援Arm架构的C语言编译器,最终可直接在Arm的环境运作并依据输入的C程式去产生原生执行档——完全不需要其他工具的辅助,这样逐步建构编译器自身。

最后,引用Dennis M. Ritchie的话来解释C语言的成功:

: C is quirky, flawed, and an enormous success. While accidents of history
: surely helped, it evidently satisfied a need for a system implementation
: language efficient enough to displace assembly language, yet sufficiently
: abstract and fluent to describe algorithms and interactions in a wide
: variety of environments.

(C语言很别扭又缺陷重重,却异常成功。固然有历史的巧合推波助澜,但也的确是因它能满足于系统软体实作的程式语言期待:既有相当的效率来取代组合语言,又可充分达到抽象且流畅,能用于描述在多样环境的演算法。」

在C语言之前就存在的「直系」亲属:

  • A:ALGOL 60
  • B:BCPL, B

就语法来说,C语言的确长得很「古怪」,不若之前那些程式语言贴近数学表示法或英文书写方式,但得益于C语言做的许多取舍,可成功地运作在多种不同的硬体环境、支援多样的作业系统,从而让UNIX (或相容的)作业系统及C语言编译器的组合,深刻地影响你我所处的数位世界。

  • 我的微信
  • 微信扫一扫加好友
  • weinxin
  • 我的微信公众号
  • 扫描关注公众号
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: