1.4 K&R C
到了20世纪70年代中期,C语言已经很接近目前这种我们所知道和喜爱的形式了。更多的改进仍然存在,但大部分都只是一些细节的变化(比如允许函数返回结构值)和一些对基本类型进行扩展以适应新的硬件变化的改进。(比如增加关键字unsigned和long)。1978年,Steve Johnson编写了pcc这个可移植的C编译器。它的源代码对贝尔实验室之外开放,并被广泛移植,形成了整整一代C编译器的基础。C语言的演化之路如图1-2所示。
图1-2 后期的C
一个非比寻常的Bug
C语言从Algol-68中继承了一个特性,就是复合赋值符。它允许对一个重复出现的操作数只写一次而不是两次,给代码生成器一个提示,即操作数寻址也可以类似地紧凑。这方面的一个例子是用b+=3作为b=b+3的缩写。复合赋值符最初的写法是先写赋值符,再写操作符,就像:b=+3。在B语言的词法分析器里有一个技巧,使实现=op这种形式要比实现目前所使用的op=形式更简单一些。但这种形式会引起混淆,它很容易把
b=-3; /* 从b中减去3 */
和
b= -3; /* 把-3赋给b */
搞混淆。
因此,这个特性被修改为目前所使用的这种形式。作为修改的一部分,代码格式器程序indent也作了相应修改,用于确定复合赋值符的过时形式,并交换两者的位置,把它转换为对应的标准形式。这是个非常糟糕的决定,任何格式器都不应该修改程序中除空白之外的任何东西。令人不快的是,这种做法会引入一个Bug,就是几乎任何东西(只要不是变量),如果它出现在赋值符后面,就会与赋值符交换位置。
如果你运气好,这个Bug可能会引起语法错误,如:
epsilon=.0001;
会被交换成:
epsilon.=0001;
这条语句将无法通过编译器,你马上就能发现错误。但一条源语句也可能是这样的:
valve=!open; /*valve被设置为open的逻辑反*/
会悄无声息地交换成:
valve!=open; /*valve与open进行不相等比较*/
这条语句同样能够通过编译,但它的作用与源语句明显不同,它并不改变valve的值。
在后面这种情况下,这个Bug会潜伏下来,并不会被马上检测到。在赋值后面加个空格是很自然的事,所以随着复合赋值符的过时形式越来越罕见,人们也逐渐忘记了indent程序曾经被用于“改进”这种过时的形式。这个由indent程序引起的 Bug直到20世纪80年代中期才在各种C编译器中销声匿迹。这是一个应被坚决摒弃的东西!
1978年,C语言经典名著The C Programming Language出版了。这本书受到了广泛的赞誉,其作者Brian Kernighan和Dennis Ritchie也因此名声大噪,所以这个版本的C语言就被称为“K&R C”。出版商最初估计这本书将售出1000册左右。截止到1994年,这本书大约售出了150万册(参见图1-3)。C语言成为最近20年最成功的编程语言之一,可能就是最成功的。但随着C语言的广泛流行,许多人试图从C语言中产生其他变种。
图1-3 像猫王艾尔维斯一样,C语言无处不在