为了敦促自己读书,特地开了一个读书笔记系列。

自上大学学习了谭浩强版本的《C程序设计》以来,以及经过这么多年遭受的C/C++面试和工作的毒打,就有一个感觉,C/C++语言的设计处处充满了陷阱。编译器设计之复杂,条目细则之多,感觉远远超出了其他语言。掌握了越多的这些”细则“或者”陷阱“,就离大师就更近了一步。而这些大师们,也津津乐道于讲述自己所掌握的这些”陷阱”以彰显自己大师之风范,以在面试中能够用这些“陷阱”困住面试者为骄傲!难怪人们常说,“细节决定成败”!而程序员最需要掌握的更加是一些编程语言、编译器的细节。

You are the master of the C language! –> 其实是个双关语! 哈哈 :)

幸好有了 Andrew Koenig《C Traps and Pitfalls》,带领我们一步步揭开 C 语言的面纱,避开一个个诡异的陷阱,走向 C 语言专家,成为 C语言大师!恨自己早些年没有读这本书!

目录

  • 词法陷阱
  • 语法陷阱
  • 语义陷阱
  • 连接
  • 库函数
  • 预处理器
  • 可移植性缺陷

概述

这本书主要是按照C语言编译链接过程顺序来排版。作者是鼎鼎大名的贝尔实验室的大牛。作者和贝尔实验室同时期的大部分鼎鼎有名的人都是同事,他们也都有帮忙协助修订本书。光我熟悉的人中就有 Dennis Ritchie(C语言设计者), Bjarne Strustrup(C++语言设计者),Rob Pike(Go语言设计者) Steve Bellovin(《Accelerated C++》和《C++ Primer》作者)等等,还有十几个名字我还没来得及去查。真的是牛人身边都是牛人!

这本书出的比较早,作者写本书的时候,ANSI C标准甚至还没有定稿,作者也是ANSI C的标准委员会成员,当然也就是制定者之一了。

一、词法陷阱

废话不多说,直接开始吧!

1.1 =不等于==

如果你问一个C语言新手在编程的时候最常犯的错误是什么,那么答案极有可能就是,

我在 if 条件里面判断的时候少写了一个等号!

1
2
3
if (x = 3) {
    printf("x = 3"); // 因为if里面是个赋值语句,表达式永远为真,该语句都会被执行。
}

是的!这也是我在C/C++编程的时候常常犯的错误!而且这种错误非常致命,因为编译器不会报告任何错误,甚至都不会有警告!这种错误 $\color{red}{非常难以排查}$

为此,许多IT公司还设计出了一个看起来不美观但是有效的办法,将操作数判断放到等号前面作为左值,这样编译器发现少了一个等号,就会报告错误!让这种错误无处遁形。

1
2
3
if (3 = x) {  // 编译器会报错,因为数字 3 不是变量,不能成为左值被赋值。
    printf("x = 3"); 
}

而许多其他语言也积极吸取了该教训,有的将赋值操作换一个操作符,=只作为相等比较。有的则在if的判断表达式里面禁用等号赋值操作,只使用一个等号的直接报错!

1.2 &| 不同于 &&||

其实是 赋值符号 和 相等 符号问题的延伸。作者认为这也是很容易犯的错误,容易漏写一个。但好像我们中国人不太容易犯这个错,至少我是很少的。

不过关于 &&|| 我倒有一点想说,这两个符号都是 短路计算。 这也算是表达式副作用的体现,我觉得后面可能会有专门一章讲解表达式和函数的副作用。

  • a && b ,如果 a 为假,则 b 不再进行计算(如果有函数调用,则不会进行调用)。
  • a || b ,如果 a 为真,则 b 不再进行计算。

1.3 词法分析中的“贪心”算法

说道贪心算法,“我要,我还要!”闪过脑海,想必大家都非常了解了! 说的简单一点就是,当从前往后识别的时候,遇到满足条件的就能够满足的就要的更多!举个栗子!

int *p;
int b = x/*p;

你们认为这段表达的意思是什么呢?我省去了语法高亮。我再把语法高亮打开,看看

1
2
int *p
int b = x/*p;

贪心算法遇到 /的时候,如果后面遇到了 * 则贪心地将这两个符号组合在一起,构成多行注释的前半部分。而不是 x 除以 *p,而要表达这个意思,则需要写作 int b = x / *p; 或者 int b = x/(*p);

所以,一个思考题:a---b 表达的是 a-- -b 还是 a- --b 呢? 而 a+++++b 表示的是什么呢?

(答案参见附录

1.4 整型常量

在 C语言程序中,整数表示有二进制、八进制、十进制和十六进制等多种表示方法。 0b01010101 010 1234 0xffff

格外需要注意 010 不是我们通常意义上的10,而是八进制的,表达的数字其实就是8。 我猜测,原本应该写为 0o10,但怎么看一个字母小写o 在数字钟都显得比较奇怪,因为跟 0 比较接近,因此直接禁掉这种写法比较干脆。 但没有了这个小欧,有些人在 代码对齐 的时候就可能忽视掉这个0开头导致数字弄错。这个需要格外注意!

1.5 字符和字符串

在C语言中,单引号引起的字符跟这个字符代表的ASCII码数字完全等价! 比如, char a = 'a';char a = 97; 是完全一样的。 'a' + 'd' 就完全等于 197

而 双引号引起的字符串,代表的是一个 字符数组常量的 指针。其实,也就是一个地址。

而如果这两者混用,通常情况下,会报告错误,但有些情况不会报错,就可能产生难以预料的错误(通常是导致程序崩溃),例如:

1
printf("\n");

1
printf('\n');

上面这个例子我们都知道,没啥问题,打印一个换行符。而下面这个例子则就迷惑多了! 掌握了这两个的区别,其实很简单,就是把 '\n'(换行符) 这个字符ASCII值(10)当做了一个字符串指针地址传给了printf函数,所以打印出来的东西可能很混乱,也可能是崩溃,属于非法访问。

第一章就这样完成了!撒花✿✿ヽ(°▽°)ノ✿!

附录

  • 1 表示 a-- -b
  • 2 表示 a++ ++ +b,会编译报错!

更多

[[20200715-c-traps-chapter-two]]