(经典)C语言陷阱和缺陷
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C语言陷阱和缺陷[1]
原著:Andrew Koenig - AT&T Bell Laboratories Murray Hill, New Jersey 07094
原文:收藏
翻译:lover_P
[译序]
那些自认为已经“学完”C语言的人,请你们仔细读阅读这篇文章吧。路还长,很多东西要学。我也是……[概述]
C语言像一把雕刻刀,锋利,并且在技师手中非常有用。和任何锋利的工具一样,C会伤到那些不能掌握它的人。本文介绍C语言伤害粗心的人的方法,以及如何避免伤害。
[内容]
·0 简介
· 1 词法缺陷
o 1.1 =不是==
o 1.2 &和|不是&&和||
o 1.3 多字符记号
o 1.4 例外
o 1.5 字符串和字符
· 2 句法缺陷
o 2.1 理解声明
o 2.2 运算符并不总是具有你所想象的优先级
o 2.3 看看这些分号!
o 2.4 switch语句
o 2.5 函数调用
o 2.6 悬挂else问题
· 3 链接
o 3.1 你必须自己检查外部类型
· 4 语义缺陷
o 4.1 表达式求值顺序
o 4.2 &&、||和!运算符
o 4.3 下标从零开始
o 4.4 C并不总是转换实参
o 4.5 指针不是数组
o 4.6 避免提喻法
o 4.7 空指针不是空字符串
o 4.8 整数溢出
o 4.9 移位运算符
· 5 库函数
o 5.1 getc()返回整数
o 5.2 缓冲输出和内存分配
· 6 预处理器
o 6.1 宏不是函数
o 6.2 宏不是类型定义
·7 可移植性缺陷
o7.1 一个名字中都有什么?
o7.2 一个整数有多大?
o7.3 字符是带符号的还是无符号的?
o7.4 右移位是带符号的还是无符号的?
o7.5 除法如何舍入?
o7.6 一个随机数有多大?
o7.7 大小写转换
o7.8 先释放,再重新分配
o7.9 可移植性问题的一个实例
·8 这里是空闲空间
·参考
·脚注
0 简介
C语言及其典型实现被设计为能被专家们容易地使用。这门语言简洁并附有表达力。但有一些限制可以保护那些浮躁的人。一个浮躁的人可以从这些条款中获得一些帮助。
在本文中,我们将会看一看这些未可知的益处。这是由于它的未可知,我们无法为其进行完全的分类。不过,我们仍然通过研究为了一个C程序的运行所需要做的事来做到这些。我们假设读者对C语言至少有个粗浅的了解。
第一部分研究了当程序被划分为记号时会发生的问题。第二部分继续研究了当程序的记号被编译器组合为声明、表达式和语句时会出现的问题。第三部分研究了由多个部分组成、分别编译并绑定到一起的C 程序。第四部分处理了概念上的误解:当一个程序具体执行时会发生的事情。第五部分研究了我们的程序和它们所使用的常用库之间的关系。在第六部分中,我们注意到了我们所写的程序也不并不是我们所运行的程序;预处理器将首先运行。最后,第七部分讨论了可移植性问题:一个能在一个实现中运行的程序无法在另一个实现中运行的原因。
1 词法缺陷
编译器的第一个部分常被称为词法分析器(lexical analyzer)。词法分析器检查组成程序的字符序列,并将它们划分为记号(token)一个记号是一个有一个或多个字符的序列,它在语言被编译时具有一个(相关地)统一的意义。在C中,例如,记号->的意义和组成它的每个独立的字符具有明显的区别,而且其意义独立于->出现的上下文环境。
另外一个例子,考虑下面的语句:
if(x > big) big = x;
该语句中的每一个分离的字符都被划分为一个记号,除了关键字if和标识符big的两个实例。
事实上,C程序被两次划分为记号。首先是预处理器读取程序。它必须对程序进行记号划分以发现标识宏的标识符。它必须通过对每个宏进行求值来替换宏调用。最后,经过宏替换的程序又被汇集成字符流送给编译器。编译器再第二次将这个流划分为记号。
在这一节中,我们将探索对记号的意义的普遍的误解以及记号和组成它们的字符之间的关系。稍后我们将谈到预处理器。
1.1 = 不是 ==
从Algol派生出来的语言,如Pascal和Ada,用:=表示赋值而用=表示比较。而C语言则是用=表示赋值而用==表示比较。这是因为赋值的频率要高于比较,因此为其分配更短的符号。
此外,C还将赋值视为一个运算符,因此可以很容易地写出多重赋值(如a = b = c),并且可以将赋值嵌入到一个大的表达式中。
这种便捷导致了一个潜在的问题:可能将需要比较的地方写成赋值。因此,下面的语句好像看起来是要检查x是否等于y:
if(x = y)
foo();
而实际上是将x设置为y的值并检查结果是否非零。在考虑下面的一个希望跳过空格、制表符和换行符的循环:
while(c == ' ' || c = '\t' || c == '\n')
c = getc(f);
在与'\t'进行比较的地方程序员错误地使用=代替了==。这个“比较”实际上是将'\t'赋给c,然后判断c 的(新的)值是否为零。因为'\t'不为零,这个“比较”将一直为真,因此这个循环会吃尽整个文件。这之后会发生什么取决于特定的实现是否允许一个程序读取超过文件尾部的部分。如果允许,这个循环会一直运行。