《C专家编程》读书笔记(1)
Unix系统
早期的unix系统并不是用C语言写的,unix的出现要比C语言早。
汇编语言编写的unix很笨拙,编写数据结构时浪费大量时间,且系统难以调试,理解困难。
![1554267667790](C:\Users\Alien You\AppData\Roaming\Typora\typora-user-images\1554267667790.png)
Thompson(贝尔实验室的研究人员)利用高级语言的特点(对比汇编语言的缺点),但又不想运行效率底下,尝试用Fortran进行一番简短而又不成功的尝试,他创建了B语言,将用于研究语言的BCPL作了简化,使B的解释器能常驻于PDP-7只有8KB大小的内存中。由于硬件系统的内存限制,它只允许放置解释器,而不是编译器,因此阻碍了使用B语言进行unix自身的系统编程。
编译器设计者的金科玉律:效率(几乎)就是一切
编译器的效率包括运行效率(代码的运行速度)和编译效率(产生可执行代码的速度)。除了一些开发和学习环境之外,运行效率起决定性作用。
有很多编译优化措施会延长编译时间,却能缩短运行时间。
还有一些优化措施(诸如清除无用代码和忽略运行时检查等)既能缩短编译时间,又能减少运行时间,同时还能减少内存的使用量。但是这些优化措施的不利之处在于可能发现程序中的无效的运行结果,优化措施本身在转换代码时是非常谨慎,如果程序员编写了无用的代码(如:越过数组边界引用对象,因为他们“知道”附近有他们需要的变量)就可能引发错误。
编译器设计者通常会提供一些编译器选项,每个程序员可以选择自己想要的优化措施。而Dennis Rithie所创造的注重效率的“New B”却获得了成功,充分证明了编译器设计者的这条金科玉律。
B语言通过省略一些特性(嵌套过程和一些循环结构),对BCPL语言作了简化,并发扬了“引用数组元素相当于对指针加上偏移量的引用”这个想法,同时保持了BCPL语言(void?)无类型这个特点,它仅有的操作数就是机器的字。Thomposon发明了++和–操作符,并把它加入到PDP-7的B编译器中。它们在C语言中依然存在,并不是由于PDP-11存在对应的自动增/减地址模型,自动增/减机制的出现早于PDP-11硬件系统的出现。尽管在C语言中:
*p++ = *s++
可以极其有效地被编译为PDP-11代码:
moveb (r0)+, (r1)+
前者并不是根据后者特意设计。
1970年开发平台转移到PDP-11以后,无类型语言很快就显得不合时宜了,这种处理器以硬件支持几种不同长度的数据类型为特色,而B语言无法表达不同的数据类型,效率也是一个问题,迫使Thompson在PDP-11上重新使用汇编语言实现了unix。Dennis Ritche利用PDP-11的强大性能,创立了解决多种数据类型和效率的“New B”语言(很快变成了“C”),它采用了编译模式而不是解释模式,并引入了类型系统,每个变量在使用前必须先声明。
C语言的早期体验
增加类型系统的主要目的是帮助编译器设计者区分新型PDP-11机器所拥有的不同数据类型,如单精度浮点数、双精度浮点数和字符等。与其他一些语言如Pascal形成了鲜明的对比。在Pascal中,类型系统的目的是保护程序员,防止他们在数据上进行无效的操作。由于设计哲学不同,C语言排斥强类型,它允许程序员需要时可以在不同类型的对象间赋值。类型系统从未在可用性方面进行过认真的评估和严格的测试。许多C程序员仍然认为“强类型”只不过是增加了敲击键盘的无用功。
开始几年C语言的主要客户就是编译器设计者,许多特性是为了编译器设计者而建立的,根据编译器设计者的思路而发展形成的语言特性有:
数组下标从0而不是1开始
这是因为偏移量的概念根深蒂固于编译器设计者的心中
C语言的基本数据类型直接与底层硬件相对应
不像Fortran,C语言不存在内置的复数类型,某种语言要素如果底层硬件没有提供直接的支持,那么编译器设计者不会在它上面浪费任何精力,C语言一开始并不支持浮点类型,直到硬件系统能够直接支持浮点数之后才增加了对它的支持。
auto关键字显然是摆设
这个关键字只对创建符号表入口的编译器设计者有意义。它的意思是“在进入程序块时自动进行内存分配”(与全局静态分配或在堆上动态分配相反)。其他程序员不必操心auto这个关键字,它是缺省的变量内存分配模式(默认)。
表达式中的数组名可以看作时指针
将数组看作指针,把它们传递到一个函数时不必忍受必须复制所有数组内容的低效率。但是,数组和指针并不是在任何情况下都是等效的(书第4章)
float被自动扩展为double
在ANSI C中情况不再如此。早期的unix程序中,float转换成double代价非常小,只要在后面增加一个每个位均为0的字即可,如果要转换回来,去掉第二个字就可以;此外,在某些PDP-11的浮点数硬件表示形式中有一个运算模式位(mode bit), 可以只进行float的运算,也可以只进行double的运算(也就是说如果要进行这两种方式的运算需要改变运算模式位),因此早期都是将运算模式固定为double是比较方便的,省得编译器设计者去跟踪它的变化。
不允许嵌套函数(函数内部包含另一个函数的定义)
简化编译器,稍微提高C程序的运行时组织结构。
register关键词
能够为编译器设计者提供线索:程序中的哪些变量是经常使用的,这样就可以把它们存放到寄存器中。这个设计可以说是一个失误,如果让编译器在使用各个变量时自动处理寄存器的分配工作,要比一经声明就把这类变量在生命期内始终保留在寄存器里要好。这虽然简化了编译器,却把包袱丢给了程序员。
unix系统的广泛使用,使得C语言茁壮成长。C语言对直接由硬件支持的底层操作的强调,带来了极高的效率和移植性,反过来也帮助unix获得了巨大的成功。