C Traps and Pitfalls
Chap 01 词法陷阱
1.=是赋值运算符,==表示相等判断。
2.&、|是位运算符号,&&、||是逻辑运算符。
3.词法分析的贪心发:每一个符号应该包含尽可能多的字符。所以:a---b《=》a-- -b。但是/*会理解为注释,例如:b=a/*p会被解析为b=a。
4.整型常量:以0开头的整型是八进制整数。
5.单引号引用的字符表示一整数,双引号引起的字符表示一个无名字符数组。
Chap 02 语法陷阱
1.理解函数声明:按照使用的方式来声明。
理解指针数组、指向数组的指针和函数、函数指针的关键在于()、[]的优先级别大于*。所以指向数组的指针int (*a)[3]需要加括号,函数指针int (*f)()需要加括号。
float* g(): float (*h)();其中g是一个函数,返回float *;h是指向函数的指针,返回float,参数为void。*h表示该函数,使用指向函数指针的方法为:(*h)()。
(*((void (*)())0))():首先将地址0转换为指向函数的指针(通过“void(*)()”,表示返回值是void的函数指针),然后通过(*f)()调用处于地址0处的函数。
PS1:关于typedef的使用:
|-定义一种类型的别名:typedef char* String; String c = null;
|-为复杂声明定义一个简单的别名:对于void(*b[10])(void(*)())其含义为:b[10]是函数指针数组,其指向的函数接受的参数为返回值为void类型的函数指针。使用typedef简化如下:
第一步:使用别名简化右边括号中(在*后面添加别名)
typedef void (*pFunParam)();
第二步:使用别名简化左边括号中的(在*后面添加别名)
typedef void (*pFun)(pFunParam)
使用如下:pFun b[10];
PS2:指针数组和指向数组的指针。
|-指针数组:int* a[3]:数组a中的元素是int类型的指针。
|-指向数组的指针:int (*a)[3]:a是指向长度为3,里面类型为int的指针。
2.优先级问题:r=hi<<4+low。+的优先级别高于<<。*p++《=》*(p++)。最好使用()。
3.注意语句结束的分号:if(a>b);《=》if(a>b){}。声明结构体、类时需要加上分号的原因是:如果不加分号,会认为是该结构定义类型之后的一个返回值。如:
Struct { int a; } main() { return 0 } //不加分号导致结构体是为main函数的返回值。 |
4.switch后面的case如果不break会接着执行。
5.函数调用:f(),f表示该函数的地址。
6.else悬挂问题:else总是与最近的if结对,当然完整使用{}会解决else悬挂问题。
If(x==0) If(y==0) Error(); Else { Dosth(); } //else不是跟x==0结合,而是跟y==0结合。 |
Chap 03 语义陷阱
1.数组的访问:
Int a[3];int *p = a;p、a、&a[0]都是指向数组a[3]首地址的指针。p++指向数组中的下一个位置。
2.字符串是包含字符串和结束符(\0)的数组,strlen输出字符长度,不包括结束符。sizeof用于计算数组长度,计算sizeof(字符串)时要包含’\0’。
3.C语言中将作为函数参数的数组声明转换为相应的指针声明,因此char s[]等价于char *s。
4.指针的赋值仅仅是指针指向的修改,不会产生新对象。
5.空指针#define NULL 0中空指针0或者NULL经常用作指针是否为空的比较,需要注意的是空指针绝对不能解引用dereference。
6.不对称边界:C语言中遵循左闭右开[x,y)的原则。
7.函数调用f(x,y)参数中的分隔符不是逗号运算符,因此不保证运算的先后(x,y执行顺序不一定)。但是f((x,y))中是逗号运算符,运算顺序确定。此外||和&&具有短路特性,也保证了运算的顺序从左到右。诸如如下语句也需要注意执行顺序的问题:
i=0; while (i<n>{ y[i++] = x[i]; } |
8.&/|/~是位运算符,||/&&是逻辑运算符,使用时最好使用括号。
9.判断int整数a+b的结果是否溢出:
If((unsigned)a+(unsigned)b>INT_MAX){} |
INT_MAX定义在limit.h中。
10.记得为main函数返回值,0表示成功非,负数表示失败。
Chap 04 连接
1.连接器的作用:连接器的输入是c编译器生成的目标模块和库文件,输出是生成的载入模块。生成载入模块时需要对每个目标模块中的对象进行检查是否已经存在:不存在就载入,存在就进行冲突处理。
2.Int a;如果位于函数体外,就是外部对象a的定义,会分配存储空间,并可能会为其分配初始值0。Int a=7;定义并初始化。
extern int a;声明a为全局变量,并未分配空间。用于说明a是在其他地方定义并分配的。全局声明甚至可以出现在函数的内部,因为对于连接器而言,声明的变量是对变量的显示引用。有了外部声明就一定有一个地方对a进行定义:int a;
3.static int;static修饰符用于减少命名冲突,限定修饰对象的作用域在本文件内。Static同样可以修饰函数。
4.函数使用之前必须声明。声明函数可以不写参数而直接写类型。
5.使用extern进行声明是,类型要一致。Extern char* pc;和char pc[]=”abc”;是不同的声明类型。
6.extern声明的一般用法:.h头文件中生命对象,.c源文件中定义该对象。
Chap 05 库函数
1.getchar函数返回的是整数,因此注意接受时不要使用char。
2.fread、fwrite用于读、写文件。如果以读打开文件,然后在不关闭文件的情况下需要写,必须调用fseek改变文件的读写状态。
3.Setbuf用于设置缓冲区大小。但是为了防止程序结束时缓冲去数据丢失,可以设置缓存为静态数组或者动态申请数据但是不释放。调用fflush强制输出缓冲区内容。
4.可以使用errno确定库函数具体的出错原因。但是应该先检查函数返回值,然后使用errno判断具体的错误。因为库函数并不要求函数执行成功一定设置errno为0。
5.可以使用signal函数捕获异步事件。
Chap 06 预处理器
1.使用宏时注意多余的空格。#define f (x) abc或许不是我们想要的定义。
2.宏不是函数。使用时注意加括号。
3.宏不是语句。__FILE__和__LINE__是内置宏,表示文件名和代码行号。
4.宏不是类型定义。
Chap 07 可移植限制
1.标识符名称的限制:标识符如函数名可能会被截断,只保证固定的长度,因此print_int和print_double或许会冲突。
2.注意整数的大小,机器是多少位的。
3.注意字符、整数的符号,是不是unsigned。
4.注意移位运算符的补位。
5.注意内存位置0,某些机器禁止读取该位置。
6.注意出发运算发生的截断。
7.注意随机数大小。
8.注意大小写转换。
9.注意内存申请函数。