我读经典(2):《C陷阱与缺陷》学习笔记

最近,在工作之余,我抽时间阅读了一下经典书籍《C陷阱与缺陷》。本书以作者的实际C语言工作为背景,详细地介绍了用C语言进行编程工作过程中需要注意的一些细节。本书篇幅不多,但却将C语言程序员普遍遇到过的问题都包括进去了。该书很值得大家一读。

本书一共九章内容,章节结构如下图所示。

我读经典(2):《C陷阱与缺陷》学习笔记

在阅读的过程中,我摘录了一些重要的语句,同时对某些观点作了相应的评论,请大家参阅。

 

0  导读

1. 程序设计错误实际上反映的是程序与程序员对该程序的“心智模式”两者的相异之处。(心智模式:人们深植心中,对于周遭世界如何运作的看法和行为)

个人观点:程序代码实际上是程序员思维的体现,你写的程序就体现了大脑中的想法。

2. 从较低的层面考察,程序是由符号序列所组成的。将程序分解成符号的过程,称为“词法分析”。

个人观点:就像一篇英语文章是由单词组成的一样,程序代码是由一个个的符号(系统自带的关键字或个人定义的变量、函数等)组成。

3. 组成程序的这些符号,又可以看成是语句和声明的序列。

 

 

1  词法“陷阱”

1. 术语“符号”指的是程序的一个基本组成单元,其作用相当于一个句子中的单词。

2. 同一组字符序列在某个上下文环境中属于一个符号,而在另一个上下文环境中可能属于完全不同的另一个符号。

个人观点:程序中的变量有局部变量和全局变量之分,名字相同的变量,在不同的函数中,会有不同的意思。

3. 编译器中负责将程序分解成一个一个符号的部分,一般称为“词法分析器”。

4. =不同于==

个人观点:在实际编程的时候,为了避免错误,可以将包含“==”的判断语句中的常量放在左边,形如:if (常量 == 变量)

5. &|不同于&&||

个人观点:&|是位运算符,&&||是逻辑运算符。

6. 每一个符号应该包含尽可能多的字符。

个人观点:程序中定义的变量名或函数名应该以最少的字符来表达其意思。

7. 如果(编译器的)输入流截止至某个字符之前都已经被分解为一个个字符,那么下一个符号将包括从该字符之后可能组成一个符号的最长字符串。

8. 除了字符串与字符常量,符号的中间不能嵌有空白(空格符、制表符和换行符)。这就引入了准二义性问题。

个人观点:如果除了字符串与字符常量,符号的中间嵌有空白,就会在编译的时候出现语法错误。

9. 如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。

10. 用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。

11. 用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符‘\0’初始化。

12. 用单引号括起的一个字符代表一个整数,而用双引号括起的一个字符代表一个指针。

个人观点:数组或字符串名实际上就是指向首位置的指针。

 

 

2  语法“陷阱”

1. 任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。

2. 最重要的两点:1) 任何一个逻辑运算符的优先级低于任何一个关系运算符;2) 移位运算符的优先级比算术运算符要低,但是比关系运算符要高。

个人观点:有关运算符的优先级,我们没有必要去强记。在需要优先计算的时候,加上()就可以了。

3. 注意作为语句结束标志的分号。

个人观点:一般而言,如果语句的后面忘记了分号,在编译的时候会报语法错误。

4. 在函数调用时即使函数不带参数,也应该包括参数列表。

个人观点:如果函数不带参数,那么在()内加上void比较好。

5. else始终与同一对括号内最近的未匹配的if结合。

个人观点:为了避免逻辑错误,要注意代码书写的版式。

 

 

3  语义“陷阱”

1. C语言中的数组值得注意的地方有以下两点:1) C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来;2) 对于一个数组,我们只能够做两件事--确定该数组的大小以及获得指向该数组下标为0的元素的指针。

个人观点:数组的名称就是指向该数组下标为0的元素的指针。

2. 给一个指针加上一个整数,与给该指针的二进制表示加上同样的整数,两者的含义截然不同。

3. C语言中,字符串常量代表了一块包括字符串中所有字段以及一个空字符(‘\0‘)的内存区域的地址。

个人观点:例如,一个字符串常量为“abcde”,它实际上在内存中占6个字节,即“abcde\0”。

4. 如果使用数组名作为参数,那么数组名会立刻被转换为指向该数组第一个元素的指针。

5. “举隅法”:混淆指针与指针所指向的数据。

6. 复制指针并不同时复制指针所指向的数据。

7. 当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。

8. 一个拥有10个元素的数组中,存在下标为0的元素,却不存在下标为10的元素。

个人观点:如定义数组为int a[10],那么下标的范围为0~9

9. 记住两个原则,特例外推法和仔细计算边界,应该完全有信心做对。

10. 对于数组结尾之后的下一个元素,取它的地址是合法的。

11. 当两个操作数都是符号整数时,“溢出”就有可能发生,而且“溢出”的结果是未定义的。

12. 为函数main提供返回值。

个人观点:用int main(void)代替void main()

 

 

4  连接

1. 一个C程序可能是由多个分别编译的部分组成,这些不同部分通过一个通常叫做连接器(也叫连接编辑器或载入器)的程序合并成一个整体。因为编译器一般每次只处理一个文件,所以它不能检测出那些需要一次了解多个源程序文件才能察觉的错误。

2. C语言中的一个重要思想就是分别编译,即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合到一起。

3. 典型的连接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体,该实体能够被操作系统直接执行。

4. 连接器通常把目标模块看成是由一组外部对象组成的。程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部对象。

个人观点:被static修饰的函数或变量只限于在定义它的文件中使用。

5. 连接器的输入是一组目标模块和库文件,连接器的输出是一个载入模块,连接器读入目标模块和库文件,同时生成载入模块。对每个目标模块中的每个外部对象,连接器都要检查载入模块,看是否已经有同名的外部对象。如果没有,连接器就将该外部对象添加到载入模块;如果有,连接器就要开始处理命名冲突。

6. 每个外部对象都必须在程序某个地方进行定义。

个人观点:未经定义的对象就不能在程序中使用。

7. 如果若干个函数需要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一个源文件中以static修饰符声明。

8. 任何C函数都有一个形参列表,列表中的每个参数都是一个变量,该变量在函数调用过程中被初始化。

个人观点:在调用函数的时候输入的参数为实参,在定义函数的时候写的参数为形参。

9. 任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或定义。

个人观点:也就是说,在使用之前就要定义好。

10. 如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。

11. 每个外部对象只在一个地方声明,这个地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。

 

 

5  库函数

1. C语言中没有定义输入/输出语句,任何一个有用的C程序(起码必须接受0个或多个输入,生成一个或多个输出)都必须调用库函数来完成最基本的输入/输出操作。

2. 一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。

3. 程序输出有两种方式:一种是即时处理方式,另一种是先暂存起来,然后再大块写入的方式。

个人观点:即时或不即时,要视程序对数据的及时性的要求而定。

4. 在调用库函数时,应该首先检测作为错误指示的返回值,确定程序执行已经失败,然后再检查errno来搞清楚出错原因。

5. 所有的C语言实现中都包括有signal库函数,作为捕获异常事件的一种方式。

6. 在许多C语言实现中,信号是真正意义上的“异步”。

 

 

6  预处理器

1. 不能忽视宏定义中的空格。

个人观点:多一个空格和少一个空格,宏被替换后所表达的意思完全不一样。

2. 宏并不是函数。

3. 宏展开可能产生非常庞大的表达式,占用的空间远远超过了编程者所期望的空间。

4. 宏并不是语句。

个人观点:与分号“;”结束的才是语句。

5. 宏并不是类型定义。

个人观点:如果要使用类型定义,可以用typedef,如“typedef int INT;”。

 

7  可移植性缺陷

1. C语言中为编程者提供了3种不同长度的整数:short型、int型和long型。

 

 

8  建议与答案

8.1 建议

1. 不要说服自己相信“皇帝的新装”。

个人观点:凡事要多多思考,并动手求证。

2. 直截了当地表明意图。

个人观点:尽量不要卖弄个人的编程技巧,以简单清晰为主。

3. 考察最简单的特例。

4. 使用不对称边界。

个人观点:使用半开半闭区间。

5. 注意潜伏在暗处的bug

个人观点:bug是找不完的,我们只能尽量减少bug的个数。

6. 防御性编程。

 

 

 

(本人新浪微博:http://weibo.com/zhouzxi?topnav=1&wvr=5,微信号:245924426,欢迎关注!)

 

我读经典(2):《C陷阱与缺陷》学习笔记

我读经典(2):《C陷阱与缺陷》学习笔记

上一篇:kindeditor的上传图片简单代码


下一篇:微博背后的那些算法