C语言与陷阱

第一章 词法陷阱

1.1
if (x = y)相当于将y的值赋给x,然后检查该值是否为零
如果将比较运算符==写成赋值运算符=,因为赋值运算符=优先级低于逻辑运算符||,其结果相当于把右侧表达式的值赋值给左值
1.2
&和|是位运算符,&&和||是逻辑运算符
1.3
C编译器读入字符使用贪心法,从左到右一个一个读入,判断已读入的字符串是否可以组成一个符号,如果可能则再继续读入
符号中间不能嵌有空白,空白分开的符号会被理解为两个符号
1.4
整型常量第一个字符是0,就会被视为八进制数
1.5
单引号括起的一个字符代表一个int整数,双引号括起的一个字符代表一个指针
printf('\n')是错误的,printf("\n")才正确
单引号中包含多个字符并不能被检测到,因为单引号代表一个int,可以容纳4个字节,最后代表的整数值与编译器有关

第二章 语法陷阱

2.1
()优先级高于*,float *g();是一个返回类型为浮点数指针的函数,float (*h)();是一个函数指针,h指向函数的返回值为浮点类型
类型转换符相当于将声明中的变量名和分号去掉,再用括号整个封装起来,如(float (*)())即是将函数指针转换为指向返回值为浮点类型的函数指针的类型转换符
fp是一个函数指针,*fp则是指针指向的函数,*fp()则是函数调用方式
声明signal函数:void (*signal(int, void(*)(int)))(int)其参数是一个信号处理函数的指针,返回值也是个该类型的函数指针,解引用得到信号处理函数
2.2
对表达式的是否为0的判断要显式说明,如if(flag != 0)
运算符最优先的是(), [], ->, .
其次是单目运算符,!, ~, ++, --等,
然后是双目运算符,其中算术运算符优先级比移位运算符>>, <<高,关系运算符再次,关系运算符中==, !=又低于其他关系运算符
接着是逻辑运算符,赋值运算符,最后是三目运算符
位运算符比&&和||优先
要注意单目运算符、三目运算符、赋值运算符的结合顺序是从右到左,其他的大部分是从左到右
2.3
struct声明要加分号
如果函数没有指定返回值,会被默认为返回int类型
2.4
switch语句中,每个case部分都要以break结尾,否则会从当前位置开始,执行接下来的所有语句
2.5
函数调用时即使函数不带参数,也要包括参数列表,即函数名后要加()
2.6
如果没有用{}封装,else就会与同一对括号内最近的未匹配if结合

第三章 语义陷阱

3.1
C语言中实际上只有一位数组,数组大小需要在编译器就以常数定下来
对于数组的操作实际是通过指针进行,数组名相当于第0个元素的指针
sizeof(p)得到的是数组p的实际内存,而p在其他场景会被转换为指针
我们可以通过p+i得到下一个元素的地址,地址在内存中的偏移量实际上是数组元素内存大小的整数倍
*(a+i)相当于a[i],也相当于i[a],因为后者可以理解为*(i+a)
3.2
strcpy和strcat的使用要先分配足够的内存
3.3
将数组作为函数参数会自动转化为指针
3.4
复制指针不会复制指针所指向的数据
修改字符串常量是未定义的
3.5
NULL实际上被定义为0,而由0转换而来的指针,编译器保证不等于任何有效的指针
0指针不允许被解引用
3.6
n个元素的数组下标为0到n-1
用第一个入界点和第一个出界点表示一个数值范围,出界点n即是数组元素个数
--nn--更快
3.7
&&和||运算符在左值能确定运算结果的时候会放弃对右侧表达式的求值
三目运算符只会对其中一个表达式求值
其他运算符,尤其是赋值运算符,对操作数的求值顺序的未定义的,
y[i] = x[i++]y[i++] = x[i]都是错误的,因为不能确定左右的运算顺序
3.8
&和|是位运算符,&&和||只会得出0和1
3.9
有符号运算要注意整数溢出
if(a + b < 0)的检查方法不可靠,因为编译器对寄存器的状态设置不确定
正确的检查溢出的方法是if(a > INT_MAX - b),保证运算结果在MAX以内
3.10
如果没给main函数设置返回值,就会返回一个随机整数
main返回值0表示程序执行成功,非0表示失败,因此不设置返回值会干扰操作系统的判断

第四章 连接

4.1
在编译过程中,连接器将每个编译好的文件连接,所有外部对象和函数都只能有一个定义,不能有两个外部对象使用相同的名称,如果声明为static,则该对象的作用域就会被限制在所在文件,不会与其他文件中同名对象起冲突
4.2
int a;变量的声明出现在函数体外,则称为外部对象a的定义,也就是常用的全局变量
extern关键字只声明了该变量,说明该变量的定义在其他地方,只是一个对外部变量的引用
每个外部对象都必须在某个地方定义,且只能定义一次
4.3
static将对象的作用域限制在一个源文件内,函数声明为static同样只能被同文件中的函数调用
4.4
函数在调用之前要声明或者定义
如果函数参数中有float,short,char则不能省略声明中参数的说明,因为变量向下转换会发生错误

char c;
scanf("%d", &c);
定义一个字符变量,使用scanf读入一个整数,因为整数的内存空间比字符大,该操作会使c附近的内存被覆盖,而编译器无法分辨
4.5
一个外部变量在一个文件中定义,而在另一个文件中声明为不同类型,编译器并不能检测出错误
4.6
每个对象只在一个地方声明,即file.h头文件,一般将函数的声明和外部对象的extern声明放在头文件,将函数和外部对象的定义放在file.c源文件,这样保证了只有一个定义,只需要file.c文件和其他文件#include "file.h"就是合法的了,相当于其他文件复制了一遍extren声明

第五章 库函数

5.1
EOF是一个在stdio.h定义的值,不同于任何一个字符,因此用char是无法储存EOF的,会发生截断操作,导致判断出现错误
5.2
fopenfwirte对文件进行输入输出操作时,要在输入和输出之间插入fseek函数的调用
5.3
setbuf(stdout, buf)指定一个字符数组buf,将所有写入到stdout的输出用buf作为缓冲区,当buf被填满或者直接调用fflush才输出buf的内容
库会在程序结束时释放buf,假如buf定义在函数体中,由于生命周期的结束被提前释放,则会发生错误
解决办法是将buf定义为静态数组或者外部变量,或者动态分配缓冲区
如果buf被指定为NULL,此时标准输出被设置为不需要缓冲区
5.4
库函数调用成功后,没有要求将errno清零,同样也不能保证调用过程中是否会设置errno,直接检查errno是错误的,正确的做法是检查函数返回值是否发生了错误,再检查errno判断出错原因
5.5
signal(signal type, handler function);signal函数指定了发生某个信号时,用指定函数处理该事件,是捕获异步事件的一种方式
malloc等复杂的库函数不应该使用信号处理函数,因为中断malloc函数会导致严重的后果,相对安全的做法是设置标志,让主程序知道一个信号的发生
对于算术错误,signal处理函数返回后还将重新执行失败操作,唯一安全的做法的打印一条消息然后退出

第六章 预处理器

6.1
宏定义会根据第一个空格分割宏和实际代码
6.2
宏定义的每个参数和整个表达式都应用括号括起来
要确保宏的参数没有副作用,宏的参数出现多少次就会被求值多少次,如果参数是x++这类会带来副作用的表达式,就会带来与预期不符的结果,另一个办法是用函数解决
6.3
宏并不等于语句,将宏作为函数语句编写会带来语法问题
assert宏是一个表达式,当左值为1时右侧被省略,当左值为0时对右侧求值,在这个过程中执行了函数
6.4
宏可以对变量类型进行说明,但使用typedef进行类型定义更加通用
同时声明多个指针的时候,宏只是简单的代码复制,只会把第一个变量声明为指针,其他的变量为指针的解引用类型

第七章 可移植性缺陷

7.1
某些编译器不能指定声明中的参数类型
7.2
对标识符的命名,有的编译器只能区别出前6个字符,而且不区分大小写
7.3
在不同机器上某些变量长度不固定,因此用typedef声明这类变量,修改只需要改动类型定义
7.4
(unsigned)c将字符c转换为无符号整数时,会发生错误,c会先被转换为int整数
正确做法是使用(unsigned char)c转换,就可以直接转换为无符号整数
7.5
无符号数进行右移,空位会被0填充,有符号数则会被符号位填充
如果移位的对象长度为n位,则每次移位的操作数要在[0,n-1]内
>>1/2要快得多
7.6
要防止解引用NULL指针,应该把程序移到不允许读取内存位置0的机器上运行
7.7
负数取模可能得出来的也是负数,要使余数在[0,hashsize)内,应该对余数进行判断,小于0则加上hashsize即可
更好的做法是保证被除数避免为负,并且使用无符号数
7.8
不同机器上rand范围可能不同
7.9
大小写转换的函数根据是否要判断参数为字母有不同的版本
7.10
realloc可以重新调整一块内存的长度
有些系统中被释放的内存还能暂时保留
7.11
数字转换为字符使用n+'0'的办法也可能出错,正确做法是使用字符串常量作为字符表"0123456789"[n]
有符号数n=-n的操作可能会发生溢出,因为大部分整型数据只能表达-2^k~2^k-1范围的数据,对最小值操作会发生溢出,正确操作是赋给无符号数进行操作
对有符号数取模,要保证余数为正数,并且防止溢出,应该先把被除数都取负数,取模后得出负的余数,再取相反数

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值