当前位置:网站首页>C陷阱与缺陷 个人阅读笔记
C陷阱与缺陷 个人阅读笔记
2022-08-10 05:35:00 【八个细胞】
一、词法
1.1 =和==
1.2 位操作符&、| 和逻辑操作符&&、||
1.3 编译器优先匹配多个字符的操作符
例:a—b; 相当于(a–) -b; 再者y=x/p; 会直接匹配注释符‘ / ’
另外,a++的结果不能作为左值,a+++++b编译器解释为((a++)++)+b但是会出现语法错误1.4 一个整形常量以0开头,该常量会被视为8进制数
1.5 '‘和"",’'括一个字符表示一个整数,""括一个字符代表一个指针
例:char *str = ‘/’; //会报错
'yes’合法但不同编译器会解释成不同的意思。练习1-1 允许嵌套注释,想注释大段代码段可以使用宏
例:
#if 0
…
#endif
二、语法
- 2.1 理解声明,函数指针、指针函数等;
(*(void (*)())0)();
- 2.2 运算符优先级,不清楚就加括号
() [] {} -> .
! ~ ++ -- - (type) * &(取址) sizeof //自右至左
* / %
+ -
<< >>
< <= > >=
== !=
&(按位与)
^
|
&&
||
?: //自右至左
赋值运算符 //自右至左
,
- 2.3 注意分号空语句
- 2.4 switch语句的break
- 2.5 函数调用即使没有参数也要包含参数列表
- 2.6 if else对齐缩进,加括号
三、语义
3.1 指针与数组
int a[5][5];
int (*p)[5]; //*p表示一个int[5]型的数组,则p是指向这样一个数组的指针。
int *q; //q是指向一个整型变量的指针。
for(p = a; p < &a[5]; p++) //a[5]是一个int[5]型的数组
for(q = *p; q < &(*p)[5]; q++) //(*p)[5]表示数组最后一个整型变量,将其地址与q比较
*q=0; //将a中的元素清0
3.2 非数组的指针
- 字符串 char *r; 一个字符串的实际长度应该是strlen( r ) + 1(+1是结束标志符)
- malloc函数的使用:
malloc声明的内存必须在不使用时及时释放
malloc有获取失败的可能,此时会返回一个空指针,要在使用前进行判空操作。
例:
char *r, *malloc();
r = malloc(strlen(s) + strlen(t) + 1);
if(!r) {
complain();
exit(1);
}
/*...*/
free(r);
3.3 作为参数的数组声明
- c语言中数组作为参数实际上是传入一个指向数组第一个元素的指针。
3.4 避免“举隅法”:复制指针不等同于复制数据!!!
- 如果多个指针指向同一份数据,那通过一个指针对该数据进行修改,其他指针指向的数据(其实就是同一份)当然也就发生了更改。
3.5 空指针并非空字符串
- NULL在c语言中define为(void*)0
3.6 边界计算与不对称边界
int a[5];
int* p;
/*虽然直接引用a[5]是不合法的,但是c语言允许用&获取实际上数组出界的地址甚至改变其的值*/
for (p = a; p != &a[5]; p++)
{
*p = 0;
}
3.7 求值顺序
- && 和 || 先对左侧求值,需要时才对右侧求值。
例:if(count != 0 && sum / count < smallaverage)
即使count为0,由于左侧为假,则右侧不被计算,因此也不会报被除数为0的错误。 - a ? b : c 先对 a 求值再根据 a 的值求 b 或 c 的值。
- 逗号运算符:(分隔函数参数的逗号不是运算符)
先对左侧求值,然后将该值丢弃,再对右侧求值。
3.8 &&、 ||、 !与&、|、~
- 逻辑运算与位运算的区别
3.9 整数溢出
无符号整数运算结果是对2的n次方取模,因此不存在溢出,一个有符号整数在与无符号整数运算时会转换成无符号整数。
当两个有符号整数运算时,就有可能溢出。
判断溢出:
1. if ((unsigned)a + (unsigned)b > INT_MAX)
2. if (a > INT_MAX - b)
3.10 为main函数提供返回值
- 成功return 0; 失败return 一个非零数。
四、连接
4.1 连接器
- 连接器将若干c源程序合并成一个整体,将编译器生成的若干目标模块整合成一个载入模块,需要解决重名冲突,解析各模块对其他模块/库的引用。
4.2 声明与定义
- 在 .h中声明, .c中定义
4.3 命名冲突与static
- static可以修饰变量和函数,可以对外部源文件隐藏。
4.4 形参、实参、返回值
- 如果函数参数没有float、char、short类型,则在函数声明中可以省略参数类型的说明。
这些类型若不声明,传入参数将自动转为int类型。
4.5 检查外部类型
- extern定义的变量类型不同导致连接时的错误,特别是数组和指针是不一样的类型。
- 未声明的标识符后跟一个开括号,默认其返回类型为整型。(解释为extern int xxx();
4.6 头文件
- 外部类型要在头文件声明。
五、库函数
5.1 getchar()
- 返回整数,若用char类型接收可能无法容下所有可能的字符,比如EOF
5.2 文件读写的兼容性问题
- 为了兼容过去不能同时对一个文件读写,一个输入操作不能立刻接一个输出操作。
可以使用fseek()函数穿插:
int fseek(FILE *stream, long int offset, int whence) | |
---|---|
stream | 这是指向 FILE 对象的指针,该 FILE 对象标识了流。 |
offset | 这是相对 whence 的偏移量,以字节为单位。注意sizeof的结果是unsigned int。 |
whence | 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一: |
- whence的取值范围:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
5.3 缓冲输出与内存分配
- 缓冲区一般定义在全局,或设置为static
- setbuf(stdout, (char*)0); //强制不允许对输出进行缓冲
5.4 使用errno检测错误
- extern int errno
5.5 库函数signal
- <signal.h>
signal(signal_type, handler_function); - 信号是真正意义上的“异步”,由于信号出现在很多复杂库函数中,因此信号的处理函数不能调用这些库函数。类似地,在信号函数中使用longjmp退出也是不安全的。
六、预处理器
- 宏只是文本替换,没有函数调用的开销。getchar和putchar经常被实现为宏。
- 6.1 注意宏中的空格
- 6.2 注意参数的副作用 括号、自增自减的副作用等
- 6.3 宏不是语句
在宏中使用if要注意它与上下文衔接的缩进。
PS:若实在想使用条件判断,可以考虑直接使用 || 表达式的方式,例如
((void) ((e) || f()))
当e为真,后半不会计算,f()将不会运行;e为假,后半才会计算,f()才会运行。 - 6.4 宏不是类型定义
特别是需要给指针类型取别名时,如果使用宏定义的展开定义多个新变量,它将达不到想要的结果。例如:
#define T1 struct foo *
typedef struct foo *T2;
T1 a, b; //相当于struct foo *a, b; 这导致a是指针类型,b却是foo类型
T2 a, b; //a,b都是指针类型
七、可移植性缺陷
7.1 c语言标准的变更
比如函数声明时的参数类型能否声明。7.2 标识符名称的限制
ANSI C标准保证的是前6个字符的区分,且不区分大小写。
当移植的编译器真的仅仅按这个标准,那么标识符的命名就必须考虑恰当。目前的标准,外部标识符是6个字符,内部是32个字符。
7.3 整数的大小
short、int、long,int足以容纳任何数组的下标
short和int至少16位,long至少32位
为了可移植性,可以对这几种类型用typedef取别名来使用7.4 字符是有符号整数还是无符号整数
7.5 移位运算符
无符号数移位是补零;有符号数移位可以填0,也可以补符号位。移位的取值范围是0到位数n-1,即一次操作不允许将某个数所有位都移出。
无符号数做除2的操作,使用右移一位比用/2的效率高。
7.6 内存位置0
NULL指针define为了(void*)0,这也意味着它指向内存地址0,一般情况下,它只允许在赋值和比较运算时使用,其他情况是非法的。
在不同的c语言编译器下,对于内存地址0有不同的限制,硬件级读保护/只读/可写可读,第三种情况当对内存地址0非法写,可能会修改操作系统的内容。
因此为了良好的可移植性和程序的健壮性,严格禁止对NULL指针的其他操作。
如图是在vcruntime.h找到的NULL的定义:
keil5环境stdio.h中的定义是直接定义为了0:
- 7.7 除法运算时发生的截断
对于c语言的除法和取余,即a=q*b+r,只满足以下的性质:
当a>=0,b>0时,有|r|<|b|,且r>=0。
这意味着a,b出现负数时并不能保证余数和商的符号,不同编译器对此也有不同的安排,因此建议为了可移植性,取余和求商的操作在无符号数上进行,需要符号时再自己转换。 - 7.8 随机数的大小
rand函数在不同c实现输出的随机数的最大值可能存在差异,ANSI C标准中定义了RAND_MAX但在较早的c实现是没有的。 - 7.9 大小写转换
即库函数toupper( c )和tolower( c ),最初是用宏实现,且不判断c的合法性。
目前ctype.h中定义的 toupper 和 tolower 都是函数。 - 7.10 首先释放,然后重新分配
UNIX的realloc函数可以保证min(oldsize, newsize)区域的内存块的数据保持不变,这意味着free(p); p = realloc(p, newsize);
也是合法的(只要free和realloc之间衔接的够快,就没有问题)
八、建议
- 尽量用括号表明意图
- 判断等于的常量可以放在左边,这样即便不小心用了赋值运算符,编译器也会报错
- 考查特例
- 使用不对称边界
- 潜伏的bug:
特别是不同c实现下某些函数的参数限制,移植性情况等,这通常在程序逻辑上是发现不了的 - 防御性编程:
比如对已知非法情况的操作;
再比如即使自己认为所有非法条件下的操作都已经写了具体的操作,但还是需要对其他情况给出一个适当的处理,以防止没有考虑到的非法情况造成系统的崩溃。(具体来说,比如所有 if 都有 else)
边栏推荐
- 树结构——2-3树图解
- pytorch-10.卷积神经网络(作业)
- 探索性数据分析EDA
- LeetCode refers to the offer 21. Adjust the order of the array so that the odd numbers are in front of the even numbers (simple)
- Pytorch - 07. Multidimensional characteristics of input processing
- 一个基于.Net Core跨平台小程序考试系统
- LeetCode 292. Nim Game (Simple)
- Bifrost micro synchronous database implementation services across the library data synchronization
- LeetCode 剑指offer 10-I.斐波那契数列(简单)
- Reprint fstream, detailed usage of ifstream
猜你喜欢
随机推荐
pytorch-06. Logistic regression
【笔记】集合框架体系 Collection
[Notes] Collection Framework System Collection
LeetCode 1720. Decoding XORed Arrays (Simple)
cesium listens to map zoom or zoom to control whether the content added on the map is displayed
链表API设计
.Net Core导入千万级数据至Mysql
我不喜欢我的代码
Day1 微信小程序-小程序代码的构成
测一测异性的你长什么样?
Likou - Number of Provinces
LeetCode 938. Range Sum of Binary Search Trees (Simple)
Pytorch - 07. Multidimensional characteristics of input processing
LeetCode 2011. Variable Value After Action (Simple)
Canal reports Could not find first log file name in binary log index file
pytorch-10.卷积神经网络
wiki confluence installation
卷积神经网络(CNN)实现mnist手写数字识别
The way for programmers to make money from a sideline business and increase their monthly income by 20K
IO stream【】【】【】