又是一本好书,这不是一本C语言教材,本书适合有一定的C语言基础的人看,本书揭示了很多C/C++程序员经常遇到的问题,就像一本C语言编程纠错本。作者关注的不是C语言语法的细节,因为语法的细节可以在任何一本C语言参考手册中获得,作者关注的是那些看上去没有问题,但其实包含很隐蔽而且极难调试bug的程序。每一个问题作者都用了一个实例来说明,而这里的实例在你的C语言编程经历中也许不只一次的遇到过。看完之后,我感觉好多内容都是以前遇到过的,还有一些是还没有遇到过,但如果遇到了肯定是要调试半天才能搞明白的,下面我总结了本书中的经典实例:
1. = 和 ==
经典错误,以前我就遇到过,在if语句中将比较操作符 == 误写成 =, 结果比较变成了赋值,调试起 来真不容易发现!
建议把常数放在 == 左边,变量放在右边,这样如果将 == 误写成 = 了编译器可以报错。如:
int a = 1;
if ( 0 == a) {
/* 代码 */
}
2. 小心八进制数
如果常数的第一个字符是0,则该数是八进制数。一个很容易犯的错误就是为了整数对齐,而在整数前补0,本来我们需要的是十进制数,但却得到了八进制数,如下例子:
struct {
int part_number;
char *description;
} parttab[] = {
046, "left-handed widget",
047, "right-handed widget",
125, "frammis"
};
3. 理解复杂的函数声明
这一节实在精彩,作者庖丁解牛般的分析复杂的函数声明,从此看到类似 (*(void(*)())0)(); 或 void (*signal(int, void(*)(int)))(int); 这样的函数声明应该可以自己分析出来了。
4. 运算符优先级
为了减少因为优先级而引起的错误,最好使用括号使要计算的表达式更清楚。
5. if...else 的匹配问题
如果if后不用{}约束的话,else将匹配与其最近的一个if 语句.
6. 指针与数组名的区别
指针是变量,可以改变和赋值,数组名不是变量,不可以改变和赋值。更形象的是指针需要一定的内存大小存储,而数组名没有与之对应的内存。
7. 非对称的边界条件
C语言中数组下标以0开始,int a[10]实际上a[10]并不是数组元素之一。
8. 表达式计算的顺序
在逻辑判断中,条件成立则会立即停止运算,如:
if (y != 0 && x/y > tolerance) {
complain();
}
而有些表达式则不能主观臆想其运算顺序,如:
i = 0;
while (i < n) {
y[i] = x[i++];
}
y[i] 和 i++ 的执行顺序是不能被保证的,也许是先计算y[i],那么程序按你想的运行,也许是先计算i++,那y[i]的值其实就成了先前y[i+1]的值了,正确的方法应该是:
i = 0;
while (i < n) {
y[i] = x[i];
++i;
}
9. 整数溢出
判断整数运算是否溢出可能很多人会这样写:
if (a + b < 0)
complain();
但这样的方法是不保险的,在有些机器上可能会失败,因为在有些机器上对于加法运算会影响内部某个寄存器的的状态:positive, negative, zero, overflow. 上面的实现方法只会检查该寄存器的状态是否为negative, 而当溢出发现时,该寄存器的状态为overflow,判断失败。
正确的方法如下:
if ((unsigned)a + (unsigned)b > INT_MAX) {
complain();
}
或
if (a > INT_MAX - b) {
complain();
}
10. 重复声明全局变量
关于重复声明全局变量的结果在CSAPP 的链接一章介绍的比较详细了,编写程序时一定要注意全局变量名不要重复,如果可能,使用static.
11. 缺少函数声明
当函数缺少函数声明时,该函数默认的返回值将为int。
12. char 型变量不一定可以表达所有的字符
很多C语言程序员会这样写:
int main()
{
char c; /* 最好声明为 int c; */
while ((c = getchar()) != EOF) {
putchar(c);
}
return 0;
}
这个程序的问题在于变量c被声明为char 型而不是integer。有可以出现它无法装下EOF这样的字符,所以最好是将变量声明为 int c;
13. 使用errno检查错误
典型的错误是:
call library function
if (errno)
complain();
或:
errno = 0;
call library function
if (errno)
complain();
正确的方法应该是:
call library function
if (error return)
examine errno
14. 宏不是函数
在使用带参数的宏的时候要特别小心,虽然其形式上与函数很像,但它不是函数,仅仅只是字符串的替换而已。
首先,将每个参数用括号括起来,并把整个表达式也用括号括起来,如下:
#define max(a, b) ((a) > (b) ? (a) : (b))
这样是为了防止当宏用在其他表达中时出现的运算符优先级问题,如果宏这样定义:
#define abs(x) x>0?x:-x
当宏这样使用时,
abs(a) + 1
它将被自动扩展为:
a>0?a:-a+1
明显错了,因为 ‘+’优先级更高。
另外一个问题是宏的操作数被计算多次,如下使用:
biggest = x[0];
i = 1;
while (i < n)
biggest = max(biggest, x[i++]);
赋值语句将被扩展为:
biggest = ((biggest) > (x[i++])?(biggest):(x[i++]));
当biggest < x[i++]时,i++将运算两次,结果就是 i 变成了 3,而不是2.
所以使用带参数的宏时应该极其小心。