C Traps and Pitfalls

来源:互联网 发布:java roundingmode 编辑:程序博客网 时间:2024/04/27 14:44

 

1.假设hi和low是两个整数,他们的值介于0到15之间,r是一个八位整数,想要r的低四位和low一致,高四位和hi一致,很自然会这样写:

 

r = hi《4+low;很不幸这样是错误的,因为加法的运算级别要比移位运算符高,因此实际上相当于,r=hi《(4+low);

  所以正确的写法是,r=(hi《4)+low或者r = hi《4 | low;

 

2.int calendar[12][31]可以看做拥有12个元素的数组,其中每个元素都是一个拥有31个整型元素的数组,这12个元素每一个calendar【X】都是所在31个元素数组的首地址,于是四月七号可以表示为*(calendar[4]+7)或者*(*(calendar+4)+7)或者calendar[4][7];

 

 

3.空指针并非空字符串。当常数零被转换为指针使用时,这个指针绝对不能解除引用(dereference).换句话说,当我们把0赋给一个指针变量时,绝对不能企图使用该指针所指向的内存中的存储内容。

 

下面的写法是合法的:

 

 

 

4.边界计算与不对称边界。

 

  循环的写法推荐写成下面的方式:

 

而不是写成下面这样:

 

 

对于指针bufptr,我们考虑“不对称边界”的偏好,让它指向缓冲区中第一个未被占用的字符,而不是指向缓冲区中最后一个已占用的字符,于是我们这样编写语句:

 

 *bufptr++ = c;

 

当指针bufptr与&buffer[0]相等时,缓冲区存放的内容为空,因此初始化时声明缓冲区为空可以这样写:

bufptr = &buffer;或者更简洁的写成 bufptr = buffer;

 

所以任何时候缓冲区中已存放的字符数都是bufptr-buffer,未占用的字符数为N-(bufptr-buffer);

 

现在编写函数bufwrite:

 

     函数bufwrite有两个参数,第一个参数是一个指针,指向将要写入缓冲区的第一个字符;第二个参数是一个整数,代表将要写入缓冲区的字 符数。假定我们可以调用函数flushbuffer来吧缓冲区里的内容写出来,而且函数flushbuffer会重置指针bufptr,使其指向缓冲区的起始位置。  注:在大多数c语言实现中,--你>=0至少与等效的n-->0一样快,甚至在某些c实现中还要快。

 

   代码中出现了bufptr与&buffer[N]的比较,而buffer 【N】元素是不存在的!数组buffer的元素下标从0到N-1,所以我们用

if(bufptr == &buffer[N])

 

等效代替了

if(bufptr > &buffer[N-1])

原因是我们坚持遵循“不对称边界的原则”:我们要比较指针bufptr与缓冲区后第一个字符的地址,实际上我们并不需要引用这个元素,只需要引用这个元素的地址。

 

5.C语言中只有四个运算符(&&、||、?:和,)存在规定的求值顺序。

&&和||首先对左侧的操作数求值,只有在需要的时候才对右侧的操作数求值。运算符?:有三个操作数:在a?b:c中,操作数a首先被求值,根据a的值再求操作数b或c的值。而逗号运算符,首先对左侧操作数求值,然后该值被丢弃,再对右侧操作数求值。

   C语言中其他所有运算符对其操作数求值的顺序是未定义的。特别的,赋值运算符并不保证任何求值顺序。

 

6.按位运算符&,|和~对操作数的处理方式是将其视作一个二进制的位序列,分别对其每个位进行操作。运算符&&和运算符||在左侧操作数的值能够确定最终结果时根本不会对右侧操作数求值。

 

7.extern关键字是一个对外部变量的引用而不是定义,static修饰符可以把变量的作用域限制在一个源文件内,对于其他文件是不可见的。我们可以在多个源文件中定义同名的函数g,只要所有的函数g都被定义为static,或者仅仅只有一个函数g不是static。

 

8.如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或者定义,那么就不会有任何与返回值类型相关的麻烦。如果一个函数在被定义或者声明之前被调用,那么它的返回值类型就默认为整型。

 

9.

 

表面上,这个程序从标准输入设备上读入5个数,在标准输出上写五个数:0 1 2 3 4

 

实际上,这个程序并不一定得到上面的结果。在某个编译器上,输出可能是:

0  0  0  0  0  0  1  2  3  4

 

  问题的关键在于,这里的c被声明为char类型,而不是int类型。当程序要求scanf读入一个整数,应该传递给他指向整数的指针。而程序中scanf函数得到的却是一个指向字符的指针,scanf并不能分辨这种情况,他只是将这个指向字符的指针作为指向整数的指针接收,并且在指针指向的位置存储一个整数。因为整数所占的存储空间要大于字符所占的存储空间,所以字符附近的内存将被覆盖。

   字符c附近的内存中存储的内容是由编译器决定的,本例中它存放的是整数i的低端部分。因此,每次读入一个数值到c时,都会将i的低端部分覆盖为0,而i的高端部分本来就是0,相当于i每次都重新设置为0,循环将一直进行。当达到文件的结束位置后,scanf函数不再试图读入新的数值到c。这时,i才可以正常的递增,最后终止循环。

 

10.在微处理器中数据总是按照高位优先的方式存放,而在内存中,会因为厂商的不同而不同。

 

     big endian:最低地址存放高位字节,称为高位优先。内存从最低地址开始,按顺序存放,高位数字先写,所有处理器都是按照这个顺序存放数据。

 

    little endian:最低地址存放低位字节,可称为低位优先。内存从最低地址开始,顺序存放。

 

11.宏中的参数最好不要有++,否则会产生很多副作用。

 

12.最好用类型定义来定义新的类型,而不实用宏来定义。例如:

 

# define T1 struct foo*;

 

typedef  struct foo *T2;

 

T1和T2从概念上完全相同,都是指向结构foo的指针,的你是如果用来声明多个变量,就会有问题:

T1 a,b;

T2 a,b;

 

第一个被拓展为struct foo * a,b;

 

这样a被定义为一个指向结构体的指针,而b被定义为一个结构体。

 

13.将基本数据类型定义成为新的数据类型使移植更加修改。

 

14.因为一个字符串常量可以用来表示一个字符数组,所以在数组名出现的地方都可以用字符串常量来替换:

 

((char)(n%10)+'0')  可以用"0123456789"[n%10]来代替

 

15.大小写字母互换的宏

 

#define  _toupper(c)    ((c)+'A'-'a')

#define  _tolower(c)    ((c)+'a'-'A')