没有合适的资源?快使用搜索试试~ 我知道了~
C缺陷与陷阱(笔记) C语言像一把雕刻刀,锋利,并且在技师手中非常有用。和任何锋利的工具一样,C会伤到那些不能掌握它的人。本文介绍C语言伤害粗心的人的方法,以及如何避免伤害
资源推荐
资源详情
资源评论
[翻译]C 语言陷阱和缺陷(修订版)
修订说明
第一次修订。改正了文中的大部分错别字和格式错误,并对一些句子依照中文的
习惯进行了改写。
译序
那些自认为已经“学完” 语言的人,请你们仔细读阅读这篇文章吧。路还长,很多
东西要学。我也是……
概述
语言像一把雕刻刀,锋利,并且在技师手中非常有用。和任何锋利的工具一样,
会伤到那些不能掌握它的人。本文介绍 语言伤害粗心的人的方法,以及如何避免伤害。
内容
简介
词法缺陷
=不是 ==
&和 |不是 &&和 ||
多字符记号
例外
字符串和字符
句法缺陷
理解声明
运算符并不总是具有你所想象的优先级
看看这些分号!
switch 语句
函数调用
悬挂 else 问题
连接
你必须自己检查外部类型
语义缺陷
表达式求值顺序
&&、||和!运算符
下标从零开始
并不总是转换实参
指针不是数组
避免提喻法
空指针不是空字符串
整数溢出
移位运算符
库函数
getc()返回整数
缓冲输出和内存分配
预处理器
宏不是函数
宏不是类型定义
可移植性缺陷
一个名字中都有什么?
一个整数有多大?
字符是带符号的还是无符号的?
右移位是带符号的还是无符号的?
除法如何舍入?
一个随机数有多大?
大小写转换
先释放,再重新分配
可移植性问题的一个实例
这里是空闲空间
参考
脚注
0 简介
语言及其典型实现被设计为能被专家们容易地使用。这门语言简洁并附有表达力。
但有一些限制可以保护那些浮躁的人。一个浮躁的人可以从这些条款中获得一些帮助。
在本文中,我们将会看到这些未可知的益处。正是由于它的未可知,我们无法为
其进行完全的分类。不过,我们仍然通过研究为了一个 程序的运行所需要做的事来做到
这些。我们假设读者对 语言至少有个粗浅的了解。
第一部分研究了当程序被划分为记号时会发生的问题。第二部分继续研究了当程
序的记号被编译器组合为声明、表达式和语句时会出现的问题。第三部分研究了由多个部
分组成、分别编译并绑定到一起的 程序。第四部分处理了概念上的误解:当一个程序具
体执行时会发生的事情。第五部分研究了我们的程序和它们所使用的常用库之间的关系。
在第六部分中,我们注意到了我们所写的程序也许并不是我们所运行的程序;预处理器将
首先运行。最后,第七部分讨论了可移植性问题:一个能在一个实现中运行的程序无法在
另一个实现中运行的原因。
1 词法缺陷
编译器的第一个部分常被称为词法分析器()。词法分析器检查
组成程序的字符序列,并将它们划分为记号()一个记号是一个由一个或多个字符
构成的序列,它在语言被编译时具有一个(相关地)统一的意义。在 中, 例如,记号-
>的意义和组成它的每个独立的字符具有明显的区别,而且其意义独立于->出现的上下文
环境。
另外一个例子,考虑下面的语句:
!"#!"$%
该语句中的每一个分离的字符都被划分为一个记号,除了关键字 if 和标识符 big 的
两个实例。
事实上, 程序被两次划分为记号。首先是预处理器读取程序。它必须对程序进行
记号划分以发现标识宏的标识符。它必须通过对每个宏进行求值来替换宏调用。最后,经
过宏替换的程序又被汇集成字符流送给编译器。编译器再第二次将这个流划分为记号。
在这一节中,我们将探索对记号的意义的普遍的误解以及记号和组成它们的字符
之间的关系。稍后我们将谈到预处理器。
1.1 = 不是 ==
从 &" 派生出来的语言,如 '( 和 &),用:=表示赋值而用=表示比较。而
语言则是用=表示赋值而用==表示比较。这是因为赋值的频率要高于比较,因此为其分
配更短的符号。
此外, 还将赋值视为一个运算符,因此可以很容易地写出多重赋值(如 a = b
= c),并且可以将赋值嵌入到一个大的表达式中。
这种便捷导致了一个潜在的问题:可能将需要比较的地方写成赋值。因此,下面
的语句好像看起来是要检查 x 是否等于 y:
$#
#%
而实际上是将 x 设置为 y 的值并检查结果是否非零。再考虑下面的一个希望跳过空格、
制表符和换行符的循环:
*+$$,,--$,.,--$$,.,#
$"#%
在与'\t'进行比较的地方程序员错误地使用=代替了==。这个“比较”实际上是将'\
t'赋给 c,然后判断 c 的(新的)值是否为零。因为'\t'不为零,这个“比较”将一直为真,
因此这个循环会吃尽整个文件。这之后会发生什么取决于特定的实现是否允许一个程序读
取超过文件尾部的部分。如果允许,这个循环会一直运行。
一些 编译器会对形如 e1 = e2 的条件给出一个警告以提醒用户。当你确实需要
先对一个变量进行赋值之后再检查变量是否非零时,为了在这种编译器中避免警告信息,
应考虑显式给出比较符。换句话说,将:
$#
#%
改写为:
$#/$#
#%
这样可以清晰地表示你的意图。
1.2 & 和 | 不是 && 和 ||
容易将==错写为=是因为很多其他语言使用=表示比较运算。 其他容易写错的运算
符还有&和&&,以及|和||,这主要是因为 语言中的&和|运算符于其他语言中具有类似
功能的运算符大为不同。我们将在第 节中贴近地观察这些运算符。
1.3 多字符记号
一些 记号,如/、*和=只有一个字符。而其他一些 记号,如/*和==,以及标
识符,具有多个字符。当 编译器遇到紧连在一起的/和*时,它必须能够决定是将这两个
字符识别为两个分离的记号还是一个单独的记号。 语言参考手册说明了如何决定:“如果
输入流到一个给定的字符串为止已经被识别为记号,则应该包含下一个字符以组成能够构
成记号的最长的字符串”(译注即通常所说的“最长子串原则”)。因此,如果/是一个记号
的第一个字符,并且/后面紧随了一个*,则这两个字符构成了注释的开始,不管其他上下
文环境。
下面的语句看起来像是将 y 的值设置为 x 的值除以 p 所指向的值:
$012012指向除数 10%
实际上,/*开始了一个注释,因此编译器简单地吞噬程序文本,直到*/的出现。换
句话说,这条语句仅仅把 y 的值设置为 x 的值,而根本没有看到 p。将这条语句重写为:
$012012指向除数 10%
或者干脆是
$012#012 指向除数 10%
它就可以做注释所暗示的除法了。
这种模棱两可的写法在其他环境中就会引起麻烦。例如,老版本的 使用=+表示
现在版本中的+=。这样的编译器会将
$3%
视为
$3%
或
$3%
这会让打算写
$3%
的程序员感到吃惊。
另一方面,这种老版本的 编译器会将
$01!%
断句为
$01!%
剩余38页未读,继续阅读
资源评论
bluesky1937
- 粉丝: 0
- 资源: 1
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功