《C陷阱与缺陷》读书笔记

时间:2022-09-27 03:13:00

1. 词法“陷阱”

  • = 不同于 == , 可以通过if( 1 == a )来避免
  • & | 不同于 && ||

  • 词法分析中的“贪心法”

    编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理策略被称为“贪心法”,也称为“大嘴法”
    举例:
    a---b 等价于 a-- - b
    y = x/*p 编译器理解为一段注释的开始,改为 y = x / *p
    a++++b的含义是((a++)++)+b,但是a++的结果不是左值,故该式错误
    老版本的C语言中允许使用 =+ 来代表 += 的含义。所以 a=-1; 被理解为 a =- 1; 即 a -= 1; 而程序员的原意可能是 a = -1;

  • 整型常量 10不等于010

  • 字符与字符串
    1. 用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。如'a'为0141或97
    2. 用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针。
      所以
      char *slash = '/'; 编译错误
      printf('\n'); 会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息。
    3. 整型数的存储空间可以容纳多个字符,因此有的C编译器允许在一个字符常量中包括多个字符。如'yes'代替"yes"不会被编译器检测到。前者的含义并没有准确定义。
  • 某些C编译器允许嵌套注释 。写一个测试程序:无论是对允许嵌套注释的编译器,还是对不允许嵌套注释的编译器,该程序都能正常通过编译,但含义却不同。

    /*/*0*/**/1
    允许嵌套注释,解释为 /* /* /0 */ * */ 1 即 1
    不允许嵌套注释,解释为 /* / */ 0* /**/ 1 即 0*1

2. 语法“陷阱”

  • 理解函数声明

    1. float *g(), (*h)();
      ()的结合优先级高于*,故g是一个函数,其返回值为float*;h是一个函数指针,h所指向函数的返回值为float
    2. 知道如何声明一个给定类型的变量,那么该类型的类型转换符可以通过以下方式得到:
      只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。
    3. 例子:从 float (*h)(); 得到 (float (*)()) 表示一个“指向返回值为float的函数的指针”的类型转换符

    void (*signal(int , void(*)(int)))(int);

  • 运算符的优先级问题

    if( flag & FLAG != 0 ) ==> if( (flag & FLAG) != 0 ) 运算符 != 的优先级高于 &
    r = hi<<4 + low; ==> r = (hi<<4) + low; 运算符 + 的优先级高于 <<
    算术5个 > 移位2个 > 关系6个 > 按位3个( &|^, 不包括~ ) > 逻辑2个( && ||, 不包括! )
    注意:上面的移位也属于按位运算符,此外,按位运算符还有~
    《C陷阱与缺陷》读书笔记

  • 注意作为语句结束标志的分号 struct logrec{};
  • switch语句 case穿透 break;

  • 函数调用

    如果f是一个函数
    f();
    f; 计算函数f的地址,却并不调用

  • “悬挂”else引发的问题:else始终与同一对括号内最近的未匹配的 if 结合;代码缩进

  • C语言允许初始化列表中出现多余的逗号,如

    int days[] = { 31, 28, 31, 30, 31, 30,                 31, 31, 30, 31, 30, 31,               };

    原因:每一行都是以逗号结尾的,这种相似性能够方便自动化的程序设计工具的处理。

3. 语义“陷阱”

  • 指针与数组

    1. C语言中只有一维数组,且数组的大小必须编译时确定。然而,数组的元素可以是任何类型的对象,当然也可以是另外一个数组,故可以仿真一个多维数组。
    2. 对于一个数组,只能做两件事:通过sizeof 确定该数组的大小;获得指向数组下标为0的元素的指针。换句话说,任何一个数组下标运算都等同于一个对应的指针运算。
    3. 例子: int calendar[12][31]; 如果calendar不是用于sizeof 的操作数,而用于其他场合,则calendar总是被转换为一个指向calendar数组起始元素的指针
      如果两个指针指向同一个数组中的元素,则两指针相减有意义
      a+i 等同于 i+a,故 a[i] 等同于 i[a]
  • 非数组的指针

    在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符的内存区域的地址。
    char *r; strcpy(r,s); 错误,r无法提供容纳字符串的内存空间
    char r[100]; strcpy(r,s); 字符数组的大小必须足以容纳包括'\0'在内的字符串s

  • 作为参数的数组声明

    C语言中会自动地将作为参数的数组声明转换为相应的指针声明。
    然而,extern char *hello; 却与 extern char hello[]; 有着天壤之别

  • 空指针并非空字符串

    当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。
    if( p == (char*) 0 ) ... // ok
    if( strcmp(p, (char*)0 ) == 0 ) ... // error,库函数strcmp的实现中会包括查看它的指针参数所指向内存中的内容的操作。
    如果p是一个空指针,printf(p); 和 printf("%s", p); 的行为也是未定义的

  • 边界计算与不对称边界

    a[10] ==> [0,10) ,10-0=10即为元素数量,避免栏杆问题

  • 求值顺序

    C语言中只有四个运算符存在规定的求值顺序: && || ?: ,

  • 整数溢出

    1. 在无符号算术运算中,没有“溢出”一说:所有的无符号运算都是以2的n次方为模
    2. 如果算术运算符的两个操作数,一个无符号,一个有符号,那么有符号整数会被转换为无符号,故“溢出”也不可能发生
    3. 当两个操作数都是有符号整数时,“溢出”可能发生,且其结果是未定义的,作任何假设都是不安全的。
    4. 例子:
      假定a和b是两个非负整型变量,需要检查a+b是否会“溢出”,想当然的 if( a+b<0 ) complain();是错误的,因为关于结果的任何假设都不可靠
      if( (unsigned)a + (unsigned)b > INT_MAX ) complain(); 注意是unsigned int可以存放的最大值是INT_MAX的近两倍,所以大于号左边不会溢出;另外,根据上面的第2点,其实只要对加号两边其中的一个操作数进行强制转换即可
      或者 if( a > INT_MAX-b) complain():
  • 为函数main提供返回值,return 0; 或 exit(0);

    大多数C语言实现都通过函数main的返回值来告知操作系统该函数的执行是成功还是失败。如果main函数并不返回任何值,那么有可能看上去执行失败。

4. 连接

  • 什么是连接器

    1. 连接器一般与C编译器分离,它不知道C语言的诸多细节,然而它却能够理解机器语言和内存布局。编译器的责任是把C源程序“翻译”成对连接器有意义的形式,这样连接器就能够“读懂”C源程序了。
    2. 典型的连接器把编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块可执行文件的实体,该实体能够被操作系统直接执行。其中,某些目标模块是直接作为输入提供给连接器的;而另外一些目标模块则是根据连接过程的需要,从包括有类似printf函数的库文件中取得的。也就是说,连接器的输入是一组目标模块和库文件,输出是一个载入模块.
    3. 连接器通常把目标模块看出是由一组外部对象组成的。每个外部对象代表着机器内存的某个部分,并通过一个外部名称来识别。程序中的每个函数和每个外部变量,如若没被声明为static,就都是一个外部对象。经过static的“名称修饰”,则不会与其他源文件中的同名函数或同名变量发生命名冲突。当不同的目标模块中包含相同名称的外部对象,此时,连接器需要处理命名冲突。
    4. splint静态程序分析工具
      静态程序分析是指使用自动化工具软件对程序源代码进行检查,以分析程序行为的技术,应用于程序的正确性检查、安全缺陷检测、程序优化等。它的特点就是不执行程序,相反,通过在真实或模拟环境中执行程序进行分析的方法称为“动态程序分析”。
  • 声明与定义

    extern关键字

  • 命名冲突与static修饰符

    如果一个函数仅仅被同一个源文件中的其他函数调用,就应该将该函数声明为static

  • 形参、实参与返回值

    函数未显式指定返回值,则假定其返回值为 int

  • 检查外部类型

    定义处long n; 但是,声明处 extern int n;
    定义处 char filename[] = "/etc/passwd"; 但是,声明处 extern char *filename; 修改,统一改成都使用char [] 或 char *

  • 头文件

    全局变量,函数原型,结构体定义,#define,typedef

5. 库函数

  • C语言没有定义输入/输出语句,通过库函数实现输入/输出操作
  • 返回整数的 getchar 函数,否则,无法和 EOF(int类型)比较
  • 缓冲输出,函数setbuf(stdout, buf); 常量BUFSIZ
  • 使用外部变量errno检测错误

    char *strerror(int errnum);
    void perror(const char *s);

  • 库函数signal

6. 预处理器

  • 不能忽视宏定义中的空格:这一规则不适用于宏调用,只对宏定义适用

  • 宏不是函数

    将宏定义中的每个参数都用括号括起来
    确保宏中的参数没有副作用,如不能使用自增操作符++

  • 宏并不是语句

    #define assert(e) if(!e) assert_error(__FILE__, __LINE__) 避免如此使用,避免造成#define替换后if 配对出错

  • 宏并不是类型定义(使用#define创建的“新类型”当含有指针* 时会出问题),类型定义使用typedef

7. 可移植性缺陷

  • 整数大小

    3种整型类型short,int,long其长度非递减;ANSI标准要求short和int至少16位,long至少32位

  • 字符是有符号整数还是无符号整数

    1. 只有在需要把一个字符值转换为一个较大的整数时,这个问题才变得重要起来
    2. 编译器在转换char类型到int类型时,需要作出选择:将字符作为有符号还是无符号数处理?前者,应同时复制符号位;后者,编译器只需要在多余的位上直接填充0即可。
    3. 如果一个字符的最高位是1,编译器是将该字符当作有符号数,还是无符号数呢?这关系到一个8位字符的取值范围是-128到127,还是0到255。而这一点,又反过来影响到程序员对哈希表或转换表等的设计方式。
    4. 如果编程者关注一个最高位是1的字符其数值究竟是正还是负,可以讲这个字符声明为无符号字符(unsigned char),确保在将该字符转换为整数时都只需将多余的位填充为0即可。
    5. 一个常见的错误认识是:如果c是一个字符变量,使用(unsigned)c就可得到与c等价的无符号整数。这是会失败的,因为在将字符c转换为无符号整数时,c将首先被转换为int型整数,而此时可能得到非预期的结果。正确的方式是使用(unsigned char)c,因为一个unsigned char类型的字符在转换为无符号整数时无需首先转换为int型整数,而是直接进行转换。
  • 内存位置0

    所有对NULL指针的操作都是未定义的

  • 大小写转换

    使用库函数toupper和tolower,而不要使用(c+'A'-'a')之类的,因为在EBCDIC字符集中,字母并不是连续存储的,所以'A'与'a'可能并不是像在ASCII字符集中一样差值固定为32,而是64

  • 移位运算符

    1. 右移时,如果被移位的对象是有符号数,那么C语言实现既可以用0填充(逻辑右移),也可以用符号位填充(算术右移)
    2. 移位操作的位数必须>=0,而严格小于n。加上这个限制条件,能够在硬件上高效地实现移位运算
  • 除法运算时发生的截断

    q = a / b
    r = a % b

《C陷阱与缺陷》读书笔记的更多相关文章

  1. C陷阱与缺陷读书笔记

    2.1理解函数声明 这一章仔细分析了(*(void(*)())0)();这条语句的含义,并且提到了typedef的一种函数指针类型定义的用法. 我们经常用到的typedef用法是用于指定结构体的类型, ...

  2. C陷阱和缺陷学习笔记

    这段时间把<C陷阱和缺陷>看了,没时间自己写总结.就转一下别人的学习笔记吧http://bbs.chinaunix.net/thread-749888-1-1.html Chapter 1 ...

  3. C的陷阱和缺陷研读笔记01

    词法分析: 编译器将程序分解成符号的方法是 从左到右一个一个字符的读入,如果该字符可能组成一个符号,再读入下一个字符 而c语言里的符号 / * =只有一个字符长, 是单字符的, /* == 一些事双字 ...

  4. 软件测试价值提升之路- 第三章&quot&semi;拦截缺陷 &quot&semi;读书笔记

    作为一个测试团队,基本的职责是:测试产品,发现缺陷,报告结果,使每个版本的测试水准稳步提升.这些价值是作为一个测试所必须具备的,发挥这些价值能够让测试获得研发团队的基本信任.这类价值分为3部分: 1) ...

  5. C陷阱与缺陷学习笔记

    导读 程序是由符号(token)序列所组成的,将程序分解成符号的过程,成为"词法分析". 符号构成更大的单元--语句和声明,语法细节最终决定了语义. 词法陷阱 符号(token)指 ...

  6. C的陷阱和缺陷研读笔记02

    宏: 宏不是函数 展开会产生庞大的表达式 #define MIN(A,B) ((A) <= (B) ? (A) : (B))MIN(*p++, b)会产生宏的副作用 剖析: 这个面试题主要考查面 ...

  7. 《c陷阱与缺陷》笔记--注意边界值

    如果要自己实现一个获取绝对值的函数,应该都没有问题,我这边也自己写了一个: void myabs(int i){ if(i>=0){ printf("%d\n",i); }e ...

  8. 《c陷阱与缺陷》笔记--移位运算

    #include <stdio.h> int main(void){ int a = 2; a >> 32; a >> -1; a << 32; a & ...

  9. 读书笔记--C陷阱与缺陷(七)

    第七章 1.null指针并不指向任何对象,所以只用于赋值和比较运算,其他使用目的都是非法的. 误用null指针的后果是未定义的,根据编译器各异. 有的编译器对内存位置0只读,有的可读写. 书中给出了一 ...

  10. 读书笔记--C陷阱与缺陷(一)

    要参与C语言项目,于是作者只好重拾C语言(之前都是C++,还是C++方便). 看到大家都推荐看看  C陷阱与缺陷(C traps and pitfalls),于是好奇的开始了这本书的读书之旅. 决定将 ...

随机推荐

  1. ubuntu桌面版打开终端Terminal的几种方法

    1. Ctrl + Alt + T 快捷键直接打开2. 在Ubuntu左上角选择File/Open in Terminal 3. 快捷键alt+F2调出Run a Command,输入gnome-te ...

  2. jQuery实现页面滚动时顶部动态显示隐藏

    http://www.jqcool.net/jquery-scroll.html 另外headroom.js也行:http://www.bootcss.com/p/headroom.js/

  3. 分布式文件系统 FastDFS 5&period;0&period;5 &amp&semi; Linux CentOS 7 安装配置(单点安装)——第一篇

    分布式文件系统 FastDFS 5.0.5 & Linux CentOS 7 安装配置(单点安装)--第一篇 简介 首先简单了解一下基础概念,FastDFS是一个开源的轻量级分布式文件系统,由 ...

  4. Java基础语法&lpar;一&rpar;&lt&semi;注释,关键字,常量,变量,数据类型,标识符,数据类型转换&gt&semi;

    从今天开始,记录学习Java的过程.要学习Java首先得有环境,至于环境的安装我就不说了,百度有很多教程,比如:http://jingyan.baidu.com/article/20095761904 ...

  5. &lbrack;LeetCode&rsqb; Minimum Genetic Mutation 最小基因变化

    A gene string can be represented by an 8-character long string, with choices from "A", &qu ...

  6. 认识浏览器请求头User-Agent

    一.定义 User Agent中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA. 它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本.操 ...

  7. AV-TEST杀毒软件能力测试(2018年1月-12月)杀毒软件排名

    2018年1月到12月,AV-TEST攻击了实验室中无数的Windows系统,在830多项单独测试中测试了7种杀毒软件和5种快捷工具. 1.测试概述 在长期测试中,实验室在各种实际场景中测试了杀毒软件 ...

  8. 图解HTTP学习笔记

    前言: 一直觉得自己在HTTP基础方面都是处于知其然,不知其所以然的样子.最近利用空闲时间拜读了一下图解HTTP,写篇博客记录一下读书笔记. TCP三次握手: ① 发送端首先发送一个带SYN标志的数据 ...

  9. JVM各垃圾收集器对比

    本随笔是<深入理解Java虚拟机 JVM高级特性与最佳实践>读书笔记. 1.JDK1.7之后的HotSpot虚拟机所包含的所有收集器如下: 解读: 1. 总共有7种垃圾收集器 2.Seri ...

  10. UVA-11478 Halum【二分】【差分约束】

    LINK1 LINK2 题目大意 给你一个n个点,m条边的有向图 有一种操作把所有到达这个点的边全部减小d,把所有从从这个点出发的边加上d 问最后是否可以让所有边的边权最小值最大 如果可以无限大,输出 ...