C陷阱与缺陷笔记
第1章 词法陷阱
严格规范词法书写,单引号与双引号不要混用。
第2章 语法陷阱
理解函数声明
1
2//程序跳转到地址0处执行
(*(void(*)())0)();1
2
3
4void (*signal(int, void(*)(int)))(int);
//typedef简化
typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);运算符优先级
语句结束标志的分号
switch
函数调用
悬挂 else 引发的问题
第3章 语义陷阱
指针与数组
1
2
3
4
5
6
7
8int a[10] = {1,2,3,4,5,6,7,8,9,10};
//以下语句都是合法的
printf("%d\n", *(a+5));
printf("%d\n", *(5+a));
//*(a+i)即数组 a 中下标为 i 的元素的引用,简记为 a[i]
//由于 a+i 与 i+a 的含义一样,因此 a[i] 与 i[a] 也具有同样的含义
printf("%d\n", a[5]);
printf("%d\n", 5[a]); //5[a] 与 a[5]是一样的,但不推荐这样的写法非数组的指针
- 字符串,注意字符串以 ‘\0’ 结尾,而 strlen() 计算长度并不将其计算在内。
作为参数的数组声明
避免举隅法
1
2
3
4char *p, *q;
p = "xyz";
//要记住 p 的值并不是字符串 "xyz",
//p 的值是一个指向由 'x','y','z'和'\0'4个字符组成的数组的起始元素的指针空指针并非空字符串
- 空指针不能使用该指针所指向内存中存储的内容,也不能解引用
边界计算与不对称边界
求值顺序
运算符&&、||和 !
整数溢出
1
if(a > INT_MAX - b) printf("溢出\n");
为函数 main 提供返回值
第4章 连接
- 什么是连接器
- 声明与定义
- extern 声明了一个对外部变量的引用,可以出现在函数内部。
- 命名冲突与 static 修饰符
- 形参、实参与返回值
- 检查外部类型
- 头文件
第5章 库函数
- 返回整数的 getchar 函数
- 更新顺序文件
- 缓冲输出与内存分配
- 使用 errno 检测错误
- 在库函数没有调用失败的情况下,并没有强制要求库函数一定要设置 errno 为0,因此 errno 的值就可能是前一个执行失败的库函数设置的值
- 库函数调用成功,仍旧可能设置 errno 的值(fopen 例子)
- 库函数 signal
第6章 预处理器
- 不能忽视宏定义中的空格
- 宏并不是函数
- 将宏定义中出现的参数用括号括起来
- 将整个结果表达式也用括号括起来
- 宏并不是语句
- 宏并不是类型定义
第7章 可移植缺陷
应对 C 语言标准变更
标识符名称的限制
整数的大小
字符是有符号整数还是无符号整数
移位运算符
内存位置0
除法运算时发生的截断
- 不保证改变被除数的正负号,商的符号也会改变并且商的绝对值不变
- 不保证当除数大于0时余数大于等于0且余数小于除数
随机数的大小
大小写转换
首先释放,然后重新分配
可移植性问题的一个例子
1
2
3
4//合法的写法
for(int i = 0; i < 10; ++i){
printf("%c\n", "0123456789"[i]);
}
第8章 建议与答案
事前周密思考
对程序组合方式的理解尤为重要
在调试程序很长时间之后,疲惫不堪的程序员开始漫无目的地瞎碰,这里试一下,那里改一点,如果凑巧程序似乎可以运行了,便万事大吉。
- 建议
- 不要说服自己相信“皇帝的新装”
- 直截了当地表明意图
- 考察最简单的特例
- 使用不对称边界
- 注意潜伏在暗处的 Bug
- 防御性编程
- 答案
- 指针不能相加但可以相减,结果是一个整数
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 飞椅档案!
评论