文/前桥和弥

在Donald C. Gause 和Gerald M. Weinberg 合著的《你的灯亮着吗?》一书中,有这样一节。

某计算机制造商开发了一种新型打印机。技术小组在如何保证打印精度的问题上非常苦恼,每次进行新的测试时,工程师都不得不花很长的时间测量打印机的输出结果来追求精确性。 丹(Dan Daring)是这个小组中最年轻但或许是最聪明的工程师。 他发明了一种工具,即每隔8 英寸就在铝条上嵌上小针。使用这个工具,可以很快地找到打印机输出位置的误差。 这个发明显著地提高了生产效率,丹的上司非常高兴,提议给丹颁发一个公司的特别奖赏。他从车间里拿了这个工具,带回办公室,这样他写报告的时候还可以仔细地研究一下。 这个上司显然还用不惯这个工具,当他把这个工具放在桌子上 的时候,将针尖朝上了。更不幸的是,当丹的上司的上司友好地坐 到桌角上,打算谈谈给丹颁发奖励时,部门内的所有人都听到了他 痛苦的尖叫声——他的屁股上被扎了两个相距8 英寸的孔。
C 语言就恰如这个工具。也就是说,它是一门
  • 为了解决眼前问题,由开发现场的人发明的,
  • 虽然使用方便,
  • 但看上去不怎么顺眼,
  • 如果不熟悉的人糊里糊涂地使用了它,难免会带来“悲剧”的语言。
C 的发展历程 众所周知,C 原本是为了开发UNIX 操作系统而设计的语言。 如此说来,好像C 应该比UNIX 更早问世,可惜事实并非如此,最早的 UNIX 是用汇编来写的。因为厌倦了总是苦哈哈地使用汇编语言进行编程,UNIX 的开发者Ken Tompson 开发了一种称为“B”的语言。B 语言是1967 年剑桥大学的Martin Richard 开发的BCPL(Basic CPL)的精简版本。BCPL 的前身是1963 年剑桥大学和伦敦大学共同研究开发的CPL(Combined Programming Lanugage) 语言。 B 语言不直接生成机器码,而是由编译器生成栈式机(Stack Machine)用 的中间代码,中间代码通过解释器(interpreter)执行(类似Java 和早期的 Pascal)。因此,B 语言的执行效率非常低,结果,在后来的UNIX 开发过程中人们放弃了使用B 语言。 在这之后的1971 年,Ken Tompson 的同事Dennis Ritchie 对B 语言做了改良,追加了char 数据类型,并且让B 语言可以直接生成PDP-11的机器代码。曾经在很短的时间内,大家将这门语言称为NB(New B)。 之后,NB 改称为C 语言——这就是C 语言的诞生。后来,主要是为了满足使用UNIX 的程序员的需要,C 语言一边接受来自各方面的建议,一边摸着石头过河般地进行着周而复始的功能扩展。1978 年出版了被称为C 语言宝典的The C Programming Language 一书。此书取了两位作者(Brian Kernighan 和Dennis Ritchie)的姓氏首字母, 简称为K&R。在后面提到的ANSI 标准制定之前,此书一直作为C 语言语法 的参考书被人们广泛使用。听说这本书在最初发行的时候,Prentice-Hall 出版社制订了对于当时存在 的130 个UNIX站点平均每个能卖9 本的销售计划(相比Lift With UNIX[2])。当然了,哪怕是初版K&R的销售量,也以3 位数的数量级超过了 Prentice-Hall 出版社最初的销售计划 。原本只是像“丹的工具”一样为了满足 自用的C 语言,历经坎坷,最终成为全世界广泛使用的开发语言。

顺便提一下,通过http://www.cs.bell-labs.com/cm/cs/cbook/index.html可以看到K&R 的网页,各语种的K&R 封面排列在一起,颇为壮观。

为什么存在奇怪的指针运算 如果试图访问数组的内容,老老实实地使用下标就可以了。为什么存在指针运算这样奇怪的功能呢? 其中的一个原因就是受到了C的祖先B语言的影响。 B是一种“没有类型”的语言。B中可以使用的类型只有word 型(也就是整型),指针也是作为整型来使用的(像浮点型这样高级的事物,你根本见不到)。B 是虚拟机上运行的解释器,这个虚拟机以word 为单位分配内存地址(如今普通的计算机以字节为单位)。由于B以word 为单位,如果指针(仅仅是表现地址的简单的整数)加1,指针就指向数组的下一个元素。为了继承这种特性,C引入了“指针加1,指针前进它所指向类型的长度”这个规则。B 语言中同样存在p[i]是 (p + i)的语法糖这样的规则。可是,这里的(p + i)只不过是单纯的整数之间的加法运算。解引用*、地址运算符&,也以几乎和C相同的形态存在于B语言中。另外还有一个理由就是,早先使用指针运算可以写出高效的程序。通常情况下,我们总是使用循环语句来处理数组,一般都写成下面的形式,
for (i = 0; i < LOOP_MAX; i++) { /* * 在这里,使用array[i]进行各种各样的处理。 * array[i]会出现多次。 */ }
array[i]在循环中会出现多次,每次都要进行相当于 array+i的加法运算,效率自然比较低。 因此,可以使用指针运算重写上面这段循环,
for (p = &array[0]; p != &array[LOOP_MAX],p++) { /* * 在这里,使用*p进行各种各样的处理。 * *p会出现多次。 */ }
尽管*p在循环内部会出现多次,但加法运算只有在循环结束的时候执行一次。 K&R p.119 中叙述了“一般情况下,使用指针的程序比较高效”。上面的说明应该可以作为这段叙述的根据吧。可是,这些无论怎样都是老黄历了。 如今,编译器在不断地被优化,对于循环内部重复出现的表达式的集中处理,是编译器优化的基本内容。对于现在一般的C 编译器,无论你使用数组还是指针,效率上都不会出现明显的差距。基本上都是输出完全相同的机器码。 总的来说,C 的指针运算功能的出现,源自于早期的C自身没有优化手段。这一点并不奇怪,请大家回想一下在前面介绍过的内容,C本来只是为了解决开发现场的人们眼前的问题而出现的一种语言。Unix 之前的OS几乎都是使用汇编写的,即使晦涩难懂,人们也不会大惊小怪。对于当时的环境,追求什么编译器优化实在有点勉为其难。因此,当初开发C 语言的时候,是完全有必要提供指针运算功能的。可是…… 不要滥用指针运算 被称为C语言宝典的K&R 指出:“一般情况下,使用指针的程序比较高效。”这完全是“那个时代的错误”。 可是,正如前面所说,对于如今的编译器,无论是使用指针运算还是下标运算,都生成几乎完全相同的执行代码。 事到如今……难道不应该放弃使用指针运算,老老实实地使用下标访问吗? 虽然K&R 被很多人奉为“神书”,可是对于我来说,它连作为菜鸟实习的资料也不够格。为什么这么说?因为在此书中,那些滥用指针的例程完全可以让你崩溃。 莫名其妙地使用像*++args[0]这样的语句,并且乐此不疲,实在让人心烦。 K&R 里面记载了下面这个作为strcpy()实现的例子:
/* 将t拷贝成s;指针版3 */ void strcpy(char *s, char *t) { while (*s++ = *t++) ; }
虽然乍一看不容易理解,但是这种写法其实是非常方便的。因为会在C 程序中经常遇到,所以我们应该掌握这种惯用写法。既然知道“乍一看不容易理解”,那就不应该这样写,难道不是吗? 满大街的C 语言入门书都在教育我们,使用指针运算比使用下标会让程序
  • ☑更有效率
  • ☑更有C 语言范儿
所谓的“更有效率”,只不过是臆想罢了。对于这种“微不足道的”优化工作,与其让人去小心翼翼地做,还不如交给编译器来干。 所谓“更有C语言范儿”好像是有些道理。如果只是为了要让程序“有范儿”,而让代码变得晦涩难懂,那么还是拜托你行行好,扔掉这种恶习吧。 在学校里,我们要完成一些课后作业。好不容易完成了一个使用下标的程序题,不料后面的那道题为“请使用指针将刚才那道题的程序重新完成一遍”。这种事常有吧。 老实说,这种事很无聊。也许你会很“威武”地依然使用下标原封不动 地把程序又写了一遍,然后交给了老师。面对老师的指责,你义正辞严:
咦,下标运算符[]只不过是指针运算的语法糖而已,在本质上这样的写法也是在使用指针啊。
尽管这样,这位可爱的老师可能还是不会放过你,于是你就急了:
行,不就是把像p[i]这样使用下标的地方,机械地一个个替换成* (p+i)嘛。
话说回来,丢了学分,我可不负责哟。在C的世界里,使用指针运算要比使用下标的写法让人感觉更“帅一些”。 但是……与其在这些无聊的地方“耍酷”,倒不如多花点时间学一些有用的知识。你要知道,作为一个程序员,还有堆积如山的知识等着你去掌握呢。 当然,什么样的规则都有例外,比如,在“一个巨大的char 数组中,参杂了各种类型的数据①,并且我们试图读取第多少字节的数据”这样的情况下,还是使用指针运算写的程序比较容易理解。 此外,作为一个C程序员连指针运算的代码也读不懂,多少有点可悲。 尽管如此,让我们至少从现在开始尽量使用下标来写新的程序,这样做对自己,以及对以后有机会阅读你的程序的人,都有好处。
作者前桥和弥(Maebasi Kazuya),1969年出生,著有《彻底掌握C语言》、《Java之谜和陷阱》、《自己设计编程语言》等。其一针见血的“毒舌”文风和对编程语言深刻的见地受到广大读者的欢迎。
本文选自《征服C指针》一书,作者前桥和弥,吴雅明译,由人民邮电出版社出版。
Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐