《C陷阱与C缺陷》笔记

一、 词法“陷阱”

1.1 =不同于==

  • =是赋值, ==是比较,不赋值。

1.2 & 和 | 不同于 && 和 ||

  • &按位与; && 与门;| 按位或; || 或门

1.3 词法分析中的贪心法

  • 词法分析具有贪心法,尽可能多的读字符,因此具有歧义的地方要善用空格来隔离。
  • y = x/*p; //编译器会将/*p认为是一段注释的开始
  • y = x/(*p) ; y=x/ *p; //下面两个为正解
  • a=-1 //错解
  • a= -1 //正解

1.4 整型常量

  • 46为十进制046会被认为8进制0x46则是16进制

1.5 函数调用

  • 函数调用一定记得加 f(), 后面的()。

1.6 “悬挂”else引发的问题

  • else 始终与同一队括号内最近的未匹配的结合,因此括号封装if, else很有必要
    正确使用括号封装的例子:
if (x==0){
	if(x<10){
		error();
   }
}else{
   z=2*x;
}
  • 错误使用的例子,因此错误使用括号封装,导致else与就近的第二个if结合,所以出错
 if (x==0){
 	if(x<10)
 		error();
    else{
	z=2*x;
    }
}

二、语义“陷阱”

2.1 malloc的使用注意事项

  • 注意判断是否分配内存成功;
  • 内存使用完毕要free掉;
  • 为字符串分配内存,则需要字符串大小加1,因为末尾有结束标志字符
char *r, *malloc();
r=malloc(strlen(s)+strlen(t)+1);
if(!r){
	complain();
	exit(1);
}
strcpy(r,s);
strcat(r,t);
...
free(r);

2.2 编程技巧之上下边界

  • 最好实现上边界减去下边界就是所属范围。如x>=16 && x<38; 则x的个数为38-16.
  • 无符号算术运算中,没有所谓的“溢出”,有符号整数和无符号整数共同运算时,有符号整数会被转化为无符号整数,所以也不存在溢出。但是都是有符号整数就会存在溢出

三、连接

3.1 连接器

3.2 声明与定义

3.3 命名冲突与static修饰符

  • 为了避免重命名一个函数或变量,可以使用static 解决,这样a的作用域会限制在一个源文件内,对于其他源文件,a时不可见的。
    static int a;

四、 库函数

4.1 返回整数的getchar函数

# include <stdio.h>
void main(){
	int c;
	while((c=getchar()!=EOF)){
		putchar(c);
	}
}

4.2 更新顺序文件

  • extern用法
  • fseek用法、fwrite用法、fread用法
  • 许多系统中标准输入输出都允许程序打开同一个文件,同时进行写入和读出操作。一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数到调用

4.3 缓冲输出与内存分配

  • 程序输出有两种方式:一种是即时处理,但会造成较高系统负担, 另一种是先暂存一部分,然后将内容部分写入,然后继续暂存,存满后一次型写入,推荐该用法。 C语言实现通常允许程序员进行实际的写操作之前控制产生的输出数量。
  • 该控制通过setbuf函数实现
setbuf(stdout, buf) //所有写入stdout的输出都使用buf作为输出缓冲区,直到buf缓冲区被填满,或者程序员直接调用fflush, buf缓冲区中的内容才实际写入到srdout中。缓冲区大小由系统文件<stdio.h>中的BUFSIZ定义。
//示例
#include <stdio.h>
main()
{
   int c;
   char buf[BUFSIZ];
   setbuf(stdout, buf);
   while((c==getchar) != EOF)
   {
   putchar(c);
   }
}
//该程序由于最后的缓冲内容随着main函数结束而被释放,会造成最后缓存区的内容丢失。因此有如下两种解决办法
static char buf[BUFSIZ];//声明为静态变量
//或者
char *malloc();//使用动态分配缓冲区
setbuf(stdout, malloc(BUFSIZ));

4.4 使用errno检测错误

4.5 库函数signal

六、宏的优势和易错点分析

6.1 宏的优势

1.提高代码的可读性;
  • 把用到的常量定义成有意义的名字; 无需函数调用,运行效率高;
2. 优化系统开销
  • 对于一些简单的操作,无需调用函数,虽然编程是强调模块化,但是函数调用时,需要保护现场和恢复现场。这些都需要耗时对于复杂的操作来讲,这些耗时可以不计,但是对于简单的操作,则效率低下。利用宏来代替简单的操作,则可以提高程序的运行效率。
  • 大多数c语言在函数调用时都会带来重大的系统开销。因此利用宏,它有函数的功能,但没有函数的开销。比如,getchar 和 putchar 经常被实现为宏,以避免每次执行输入或者输出一个字符这样简单的操作,都要调用相应的函数而造成系统效率的下降。
3.可维护行好;
  • 对于用得比较多的常量或者简单操作,一旦需要修改,则只需要修改宏定义处,不需要逐条修改。

6.2 宏定义的空格不能忽视

  • #define f (x) ((x)-1) //由于空格的插入,导致宏定义出错,含义为f代表 (x) ((x)-1)
  • 而如下定义是正确的,另外,这一规则不用于宏调用,而只对宏定义适用。即f(3) 与 f (3) 所求值一样。
  • #defne f(x) ((x)-1)·

6.3 宏不是函数

  • 宏定义函数时把每个参数都用括号括起来;
  • 整个表达式也应该用括号括起来,以防止当宏用于更大一些的表达式中可能出现的问题
# define abs(a,b)  ((x)>0 ? (x):(-x)) //正解

//错解一

# define abs(a,b)  ( x>0 ? x : -x)
abs(a-b)
//等价为
a-b>0? a-b : -a-b //因为-a-b相当于(-a)-b,因此上式会得到一个错误的结果 

//错解二

# define abs(a,b)  (x)>0 ? (x):(-x)
abs(a)+1
//等价为
(x)>0 ? (x):(-x)+1 //相当于x 与 -x+1 作比较
  • 定义宏函数时,慎用 i++ 等类似操作

6.4 宏不是语句

6.5 宏不是类型定义

  • 常见的错误用法
# define mm int
mm* a,b;//出错,b并不是指针类型
  • 改用 typedef 可以避免上述错误
typedef int mm
mm* a,b;

七、可移植性缺陷

  • 可移植性是一个涵盖范围很广的主题,推荐丛书Mark Horton的著作《How to Write Portable Software in C》

  • 7.1标识符名称的限制
    • 某些c语言实现把一个标识符出现的所有字符都作为有效字符,有的c实现却会自动截断一个长标识符。
  • 7.2 整数的大小
    • 不同机器的最小字符长度导致的移植性缺陷。
  • 7.3 字符为有符号还是无符号
    • 字符值转换为整数值时的考虑:将其作为有符号还是无符号处理?不同的编译器有不同的处理,如果处理成无符号数,则直接在多余位上填充9,而处理为有符号数,编译器在转换时还应该复制符号位。解决办法为字符声明为无符号字符(unsigned char)。这样任何编译器都在转换时按照无符号整数处理。
  • 7.4 移位运算符
    • 向右移位时,如果被移对象是无符号数,空出的位由0填充;如果为有符号数那么既可以被0,也可以被符号位副本填充。
    • 移位计算允许的取值范围是多少?如果被移位对象长度为n位,那么移位计算必须大于或等于0,而严格小于n
  • 7.5 内存位置0
    • null指针不指向任何地址,因此其本身也不能赋值;
  • 7.6大小写转换
    • toupper() 和 tolower() 函数的历史:有的编译器可以直接将所有不论是否大小写统一的内容,均转换为大写或小写,但是有的编译器就需要提前判断。
  • 7.6 首先释放,然后重新分配
    • C语言提供的内存分配函数:malloc., realloc和free。调用malloc(n) 将返回一个指针,指向一块重新分配的可以容纳n个字符的内存。利用free函数,可以释放该内存。
    • realloc需要把指定的一块已分配内存的区域指针以及这块内存新的大小作为参数传入,这样就可以调整这块内存区域为新的大小

posted on 2019-08-02 15:31  Nancy_Fighting  阅读(151)  评论(0编辑  收藏  举报

导航