C Traps and Pitfalls (C语言陷阱和缺陷) 读书总结

来源:互联网 发布:淘宝店铺突然不存在了 编辑:程序博客网 时间:2024/06/18 13:44

又是一本好书,这不是一本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 functionif (errno)complain();
或:

errno = 0;call library functionif (errno)complain();
正确的方法应该是:

call library functionif (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. 

所以使用带参数的宏时应该极其小心。

原创粉丝点击