C陷阱与缺陷 之 各种知识技巧

1.词法分析中的“贪心法”   编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分:如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。(需要注意的是除了字符串与字符常量,符号的中间不能嵌有空白)

   eg:

y = x/*p     //p指向除数,而实际上/*会被编译器理解为一段注释的开始,编译器讲会不断的读入字符直到*/出现为止

改为下列 

y = x / *p    //或者更加清楚一点写作 y = x / (*p)

eg:

a+++++b   //请理解其含义是什么?

上式唯一有意义的解析方式是:

 a++   +   ++b

可是我们也注意到根据贪心发规则上式应该被分解为 

((a ++ ) ++ ) + b    //这样是错误的,因为a++的结果不能作为左值

因此编译器不会接受a++作为后面的++运算符的操作数。

对于为何a++为何不能做为左值是因为

首先对于i++的实现是:  

              

  int   temp;  
  temp   =   i;  
  i   =   i+1;  
  return   temp;  

而++i 的实现是:

i = i + 1;
return i;

所以对于我们提出来的问题已经能得到解决了:

i++=5; 是错误的是因为i++返回的是编译器自动分配的临时变量temp,而这个temp并不是你程序中定义的可寻址变量的引用 ,也就是说你不能通
过地址对temp进行操作.(换句话说就是不能作为左值),因为temp是一个临时变量

 

 

 

 

2. 函数语法声明  函数指针的理解

float   *g(),   (*h)();
//g是一个函数,该函数的返回值类型为指向浮点数的指针。
//而h是一个函数指针,h所指向的函数的返回值为浮点类型。
//于是我们可以得到如下;
(float  (*)  ()  )
//表示一个 “指向返回值为浮点类型的函数的指针” 的类型转换符

当然我们可以使用typedef来解决这种复杂的函数指针的类型转换符

typedef float (*HANDLER) ();
float (*(HANDLER)  fxx ) ();

例如:

当计算机启动的时候硬件讲调用首地址为0位置的子例程。我们必须设计一个c语句以显示调用该子例程,可得如下语句:

(* ( void  ( * ) () )  0) () ;

分析如下: 如果fp为一个指向返回值为void类型的函数的指针,那么(*fp)()的值为void,fp的声明如下:

void  (*fp) () ;

但是,我们一旦知道如何声明一个变量,也就自然知道如何对一个常数进行类型转换,将其转型为该变量的类型: 只需要在变量声明中的变量名去掉即可。

//因此,将常数0转型为“指向返回值为void的函数的指针”类型,可以如下写:
( void (*) () ) 0
//因此,我们可以用 ( void (*) () ) 0 来替代fp , 从而得到:
( * (void  (*) () ) 0 )  () ;
//末尾的分好; 使得表达式成为一个语句

//使用typedef 能够使表述更加清晰
typedef void (* funcptr) () ;
( * (funcptr) 0) ();

 

 

3.某比较

  a与b的相对大小顺序是否和c与d的相对大小顺序一样,可以这样写:

 

a<b  == c<d

 

4.返回整数的getchar()

   首先我们考虑下列程序:

#include <stdio.h>

main() {
    char c;
    while( (c = getchar()) != EOF )    //这里c被声明为char类型,而不是int.这意味着c无法容下所有可能的字符,特别是,可能无法容下EOF
        putchar(c)    
}

于是,在使用 c=getchar() 时,我们应该把c声明为int类型

 

5.更新顺序文件.(-*- 程序打开一个文件,同时进行写入和读出操作)

一般按照常理我们也许会向下面这样写:

FILE *fp;
struct record rec;
...
while(fread( (char*)&rec,  sizeof(rec), 1, fp) == 1 ) {
    /* 对rec执行某些操作*/
    if( /* rec必须被重新写入 */) {
        fseek(fp, -(long)sizeof(rec), 1);
        fwrite( (char* )&rec, sizeof(rec), 1, fp);  //fseek函数要求第二个参数是long类型; sizeof() 返回时一个unsigned值,因此首先必须将其转换为有符号类型才有可能将其反号
    }    
}

但是这样依然会出现问题!

如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用

改进的程序如下:

while( fread( (char*) &rec,  sizeof(rec), 1, fp) == 1) {
    /*对rec执行某些操作*/
    if( /* rec必须被重新写入 */) {
        fseek(fp, -(long)sizeof(rec), 1);
        fwrite( (char* )&rec,  sizeof(rec), 1,  fp);
        fseek(fp, 0L, 1);
    }      
}

 

6.缓冲输出与内存分配

程序的输出到终端有2种方式:1是即时处理方式;2是先暂存起来,然后再大块写入的方式,因为前者往往造成较高的系统负担

setbuf(stdout,  buf);//以库函数setbuf实现来控制输出缓存,该语句将通知输入/输出库,直到buf缓冲区被填满或者直接调用fflush函数,buf缓存区中的内容才实际写入到stdout中。
//其中缓冲区大小由系统头文件<stdio.h>中的BUFSIZ定义

下面程序作用是把标准输入中的内容复制到标准输出中(但有错误):

#include <stdio.h>

main() {
    int c;
    char buf[BUFSIZ];
    setbuf(stdout, buf);

    while( (c = getchar()) != EOF ) 
              putchar(c);
}

我们可以思考一下buf缓冲区最后一次被清空是在什么时候?答案是在main函数结束之后,作为程序交回给操作系统之前C运行时库所必须进行清理工作的一部分。但是,在此之前buf字符数组已经被释放了。

要避免这种类型的错误有两种办法。

第一种办法是让缓冲区数组成为静态数组,static char buf[BUFSIZ];  当然也可以将buf的声明完全移动到main函数之外。

第二种办法是动态分配缓冲区,在程序中并不主动释放分配的缓冲区:char *malloc();  setbuf(stdout, malloc(BUFSIZ));

 

7.宏定义中的各种需要注意的部分:

   1.不能忽视宏定义中的空格  eg:#define f (x) ((x)-1)   //这里表示的为f 代表(x) ((x)-1)

   2.宏定义中的参数要加(),避免传参中有类似a-b之类的参数存在

   3.在宏中传入参数会有副作用将导致难以想象的错误:

#define  max(a,b) ((a)>(b) ? (a) : (b))

biggest = max(biggest, x[i++]);  //这里在宏中展开就成了:( (biggest)>(x[i++]) ? (biggest) : (x[i++]) );
//这里很有可能i++会执行2次

   4.宏并不是语句

   5.宏并不是类型定义

#define T1 struct foo *
typedef struct foo *T2;

T1 a, b; //但是这里被扩展为struct foo * a, b; 这里a被定义为一指针,b被定义为一个结构体
T2 a, b;

 

posted on 2015-01-26 23:43  Zachary_wiz  阅读(298)  评论(0编辑  收藏  举报

导航