C traps and pit falls----摘记(二)2008-03-27

第六章 预处理器

使用预处理有两个用处
1.可以设定各个源文件都使用的常量
2.可以实现成函数作用,避免设计成函数后调用所产生的系统开销

要注意的是,宏只是对程序的文本起作用.所以它可以让完全不合法的代码成为一个有效程序

6.1 不能忽视宏定义中的空格

要注意宏调用和宏定义之间的区别:有关之间的空格问题
#defined f(x) ((x)-1)   和  #defined f (x) ((x)-1)
上面两个是截然不同的,但是对于宏调用
f(3) 和f (3)的结果都是2,不管调用的时候中间是不是有空格

6.2 宏不是函数

首先,要保证宏的参数没有副作用
比如说对于这样一个宏
#define max(a,b) (a)>(b)?(a):(b)
如果其参数是这样
max(bigger,x[i++]);
那么经过展开之后就会出错,i可能会求值两次
还有宏展开之后可能是一个非常庞大的表达式,占用的空间可能也会超出当初预期的想象
相比来说,有时候,使用函数的时候更加有利

6.3 宏不是语句

从assert这个宏入手
其参数是一个表达式,如果该表达式为0,则程序停止执行,并给出错误信息
我们可以先写一个
#define assert(e) if(!e) assert_error(__FILE__,__LINE__)
这里面实际上是包含许多问题的,比如
if(x>0&&y>0)
  assert(x>y);
else
  assert(x<y);
展开就会发现许多问题
if(x>0&&y>0)
  if(!(x>y)) assert_error(....);
else if(!(x<y)) assert_error(....);
这是与我们的初衷是相反的
下面给出它的正确定义
#define assert(e) /
        ((void)((e)||assert_error(__FILE__,__LINE__)))
这里面实际上是利用了"||"顺序求值的性质.

6.4 宏也不是类型定义

考虑以下两个类型定义方式
#define T1 struct foo *
typedef struct foo *T2
T1和T2概念上一样,都是指向foo结构的指针,在定义多个变量的时候会出现问题,比如
T1 a,b
T2 a,b
展开之后得到
struct foo* a,b;
struct foo*a;----struct foo * b;
只有第二个才是真正符合自己的意思   

第七章 可移植性缺陷

这里面只随便摘录一些觉得需要注意的地方
移位操作过程中,如果C实现是将符号位复制到空出的位中,有符号整数的向右移位也不等同于除以2的整数幂
举例来说,如果low+high<0,那么
mid=(low+high)<<1;
和mid=(low+high)/2
结果是等效的,但是前者的执行速度要快的多

内存位置0
null指针并不指向任何对象,除了用于赋值和比较等操作等过程中,其他调用NULL指针都是非法的

内存:首先释放,然后重新分配
    三个内存分配函数:malloc,relloc,free
    调用realloc函数,需要把指向一块已分配内存的区域指针以及这块内存新的大小作为参数传入,就可以调整(扩大和缩小)这筷内存区域为新的大小
在某些情况下,即使这块内存已被释放,realloc仍然可以继续工作,下面的代码在某些系统是合法的
free(p);
p=realloc(p,newsize);

一个可移植性问题的例子

某个函数接收两个参数输入,一个long型整数,一个函数指针,这段程序的将给出的long整数转化为10进制表示,并对该十进制的每个字符调用函数指针指向的函数
void printnum(long n,void (*p)())
{
  if(n<10)
  {
    (*p)("-");
    n=-n;
  }
  if(n>10)
  printnum(n/10,p);
  (*p)((int)(n%10)+'0');
}
这段程序并不是很麻烦,但是却存在着移植上面的几个问题,比如说
在个位数(n%10)上加上'0'来获得相应的字符并不是十分前当,该操作实际上需要假定机器的字符集中数字是按照顺序排列的,无间隔的
这种假定对ASCII和EBCDIC字符集是正确的,对ASNI C实现也是无误的.我们可以使用一张代表数字的字符映射表,参照
"0123456789"[n%10]
第二个问题就负数的溢出问题,也就是说,如果直接将负数通过添加负号转为正数,有可能会溢出,比如说,k位整数,它所能表示的范围应该只有
(-2^(k-1)--2^(k-1)-1)
但是我们可以让所有的整数都是变成负数,让所有的整数操作都是针对负数来处理,这样便不会出现将负数转为正数时会出现的溢出问题
无论n是正是负,,都是以该数的绝对值的相反数来处理
void printneg(long n,void (*p)())
{
  if(n<=10)
    printneg(n/10,p);
  (*p)("0123456789"[-(n%10)]);
}
void printnum(long n,void (*p)())
{
  if(n<0)
  {
    (*p)("-");
    printneg(n,p);
  }
  else
  {
    printneg(-n,p);
  }
}
这样做还是有个问题,当n为负数的时候,n%10仍然有可能还是个正数,这当然和它具体的C实现有关,但不能排除,所以再做以下修改
这时我们可以创建两个临时变量来保存余数和商,并判断余数是否在合理范围之内,于是我们对printneg进行修改
void printneg(long n,void (*p)())
{
  long q;
  int r;
  q=n/10;
  r=n%10;
  if(r>0)
  {
    r-=10;
    q++;
  }
  if(n<=10)
    printneg(q,p);
  (*p)("0123456789"[-(n%10)]);
}

::::::::::一些建议::::::::::::

直截了当的表达自己的意思
尽量让自己程序的意思简单明了,适当的多加括号.....

考察最简单的特例
无论是构思程序的工作方式,还是测试程序的工作情况,这一原则都是适用的
当部分数据为空或者只有一个元素的的时候,大部分程序都会执行失败,而这都是一早就该考虑到的
我们应该能从最简单的特例中获得启发.

使用不对称边界
这一部分还是很值得一看的,C数组从0开始计数,这多少会造成一些计数上的失误,,所以更应该去避免这方面的错误

尽量使用C各种实现版本的共性部分
这样做的话,在移植平台的时候使用一些众所周知的东东,都会避免不同C实现所造成编译差异

防御性编程
对程序用户和编译器实现假设不要太多!
再怎么不可能发生的事情,在某种情况下还是可能会发生的,一个健壮的程序应该预先考虑到这种异常情况.

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值