读完《C 陷阱与缺陷》感

o1

书并不厚,一百多页。昨天终于抽出一下午的时间在图书馆把剩下的一半看完,虽只是第一遍,但还是有些感想。

02

C 强大的灵活性导致了任何一个 C 代码都可能隐藏着难以察觉的深层次的错误,而往往越难以发掘的错误所带来的后果往往更加严重,甚至是系统级别的灾难性后果!C 语言标准中的各种语法规则简短而简单,但是当它们放到一起,组成一句或一段代码的时候,情况就会变得很复杂。越是接近硬件、接近底层的操作,越容易引发错误。我们在设计程序之初就应该想到这个程序未来可能遇到的方方面面的问题,例如程序的可移植性、与旧 C 编译器的兼容性、代码的健壮性、程序的生命周期等等。表面上显而易见的东西,但当他们执行的时候可能就会在很大程度上偏离程序员对他们行为的预测。隐含的错误最为致命,最稳妥的办法就是在开始敲下第一个字符的时候,你就已经知道了自己的目的和所要面临的可以想到的问题,以及他们的解决方法。

int i;
char a[n];
for(i = 0; i <= n; i++){
    a[i] = 0;
}

很明显,在最后一个循环的时候,i超出了数组的边界。C 标准中对于这种情况的所导致的结果是未定义的,但在有些计算机上,它将是一个死循环。当进入最后一个超出数组边界的循环的时候,将 0赋值给了a[i],此时,内存中位于 &a[i]地址上的数据将被修改为0,如果此编译器按照内存地址递减的方式来给变量赋值,那么这个地址将是整型变量i的地址。如果将他修改为0,那么这个循环永远不会到达边界条件,将会无限循环下去。

03

宏使得许多简单的实现得以以一种便于观看和理解的方式呈现在代码当中,但是它的危险性同样存在。

#define f (x) (x + 1)

上述是定义了一个 f(x) (x +1),还是 f(x)(x + 1)。正确的答案是前者,注意宏定义当中的空格。

#define MAX(a,b) ((a) > (b)? (a):(b))

这段宏定义看起来没有问题,但是很遗憾,它会导致一个致命问题。在宏定义MAX(a,b)中,参数ab将会被各自求值两次,试想一下,如果传入宏中的参数带有副作用例如自增或自减符(–、++),那么就会导致程序的逻辑错误。

int a[n];
int max = a[0];
int i = 0
while(i < n){
    max = MAX(max, a[i++]);
}

如果 MAX 为宏,那么就会出错。将宏展开之后可以得到

max = (max)>(a[i++]?(max):(a[i++]))

如果此时max确实比 a[i]大,那么 max没有改变,仍被赋值为 max,同时 i执行自增加一,看起来都很正确。但是如果一旦max小于a[i],那么在比较运算完成之后,首先i执行自增,再将max赋值为a[i++],但此时,这个i已经是之前自增过的i了,实际上赋值给max的值是a[i+1],而不是a[i],之后i再次执行自增。这当然已经发生了严重的错误!

04

这些只是冰山一角,我才发觉,之前我写的那些 C 程序原来就是在非常巧合的情况下才正确执行的,它们并不是真正正确的,即使顺利通过编译以及简单的功能测试,其原因只是因为巧合,仅此而已。

我将系统的记录以及分享,同步在此博客和 Wiki(没建好)上,期待我有更多的时间和经历用来阅读、思考和体会。

祝一切安好。


最后修改于 2020-10-15

此篇文章的评论功能已经停用。