【C陷阱和缺陷】可移植性缺陷

http://blog.csdn.net/tianshuai11/article/details/8201879

一,应对C语言标准变更

 编译器并不是都实现了C标准(ANSI),不同的C语言实现会有细微的差别。

 使用最新的C特性会更容易编写而且不容易出错,但可能造成在某些早期的编译器上无法工作,失去部分客户。为了提高可移植性,要在新旧用法之间进行取舍。

二,标示符名称的改变

         某些C语言的实现把一个标识符中处出现的所有字符作为有效字符处理,而有的C实现会自动截断一个长标识符的名称的尾部。

         ANSI标准所能保证的只是,C实现必须能区分前6个字符不同的外部名称,而这个定义中不区分字母的大小写。因此print_fieldsprint_float这样的名称不恰当,StateSTATE这样的命名方式也不明智。

三,整数的大小

        对于不同长度的整形:shortintlongC语言规定:

            (1) 三者的长度是非递减的

            (2)一个int整数足够大以能够容纳任何数组的下标;

            (3)字符长度由硬件长度决定。

        因为不同的整形长度在不同位的机器上的实现不同,为了提高可移植性,最好声明整形变量为long。可以在程序开始定义一个新类型名,以后一改全改:

               typdef long tenmil ;

四,字符是有符号整数还是无符号整数

        在将一个字符值转换为整数值时,有的编译器会将字符作为有符号数处理:将符号位进行复制;而有的编译器将其作为无符号数,在其多余的位上直接填0

        如果编程者关注一个最高位为1的字符的数值是正还是负,那么可将这个字符声明为unsigned char型,这样在转换为整数时,无论什么编译器,都会简单的在其空位填0.

             (unsigned) c可得到与c等价的无符号数的认识是错误的,因为在将c转换为无符号数的时候,c会首先被转换为int型整数,从而可能得到意想不到的结果。

      正确的做法:

             (unsigned char) c,因为一个unsigned char型的字符在转换为无符号整数时无需被转换为int型,而是直接进行转换。

下面是在vc6.0上的测试:

char c = -25;

int d = (unsigned char)c;

cout<<c<<endl;//nothing

cout<<d<<endl;//231

char c = 125;

int d = (unsigned char)c;

cout<<c<<endl;//}

cout<<d<<endl;//125

五,移位运算符

1)向右移位时,如果是无符号数,则左侧空出的位填0,如果是有符号数则有的C语言实现0填充空出的位(逻辑移位),有的用符号位的数字填充空出的位(算术移位)。

2)如果移位对象是n位,则允许移位的数目必须大于等于0,严格小于n

       即n如果是int型,则n>>32,n>>-1都是非法的。

3)有符号的向右移位运算并不等同于除以2的某次幂!

            -1>>1

        一般不为0(在vc6.0中为-1),而(-1)/20.

           -3>>1

       在vc6.0中为-2,而不是-1

4)移位运算要比除法运算快得多

        mid = (low + high) >>1;

        要比mid = (low+high)/2快得多,但这里要注意low+high要确保为非负。

六,内存位置0
         null 指针并不指向任何对象,除非用于赋值或比较运算,出于其他目的使用 null 指针都是非法的。

         有的C语言实现允许对内存位置0进行读,有的允许读和写,有的不允许读。即使允许读,其内容一般也是一堆垃圾信息。

              char * p = NULL;

              cout<<*p<<endl;

         因此这段程序在某些机子上输出“一堆垃圾信息”,有的运行失败。

七,除法运算时发生的截断

        对于a除以bq,余r

         q = a/b;       r=a%b;

         我们希望满足以下三条:

              1) q*b+r = a; 

              2)如果a的正负号改变,则q的符号也跟着改变,但其绝对值不变;

              3) b>0时,r>=0 && r<b.例如当余数用于哈希表的索引,则确保它是一个有效的索引值。

     但3条不可能同时成立,大多数程序设计语言放弃了第3条,只余数与被除数的符号保持相同。

         C语言只保证第一条,以及当a>=0,b>0时,|r|<|b|,以及r>=0;这比第2条和第3条的限制性要弱得多。

八,随机数的大小

         ANSI中定义了RAND_MAX,等于随机数的最大取值,早起的c实现没有包含这个常数。

         Cout<<RAND_MAX; //32767    #include <stdlib.h>

九,大小写转换

        起初大小写转换被实现为宏:

               #define toupper(c) ((c)>=’a’ &&(c)<=’z’ ? (c)+’A’-‘a’ : (c))

               #define tolower(c) ((c)>=’A’ && (c)<=’Z’ ? (c)+’a’-‘A’ : (c))

        但如果遇到类似toupper(*p++)的情况时,后果不堪设想;

        故后来又实现为函数:

int toupper(c)

{

     if(c >= ‘a’ && c <= ‘z’)

              return c + ‘A’- ‘a’;

      return c;

}

      但这样增加了函数调用的开销。

十,首先释放,然后重新分配

         调用realloc时,将指向一块已经分配内存空间的指针和要重新分配的大小传递给realloc,可以调整这块内存大小,期间可能涉及到内存拷贝。

         在第七版UNIX参考手册中,规定如果指针指向的是最近一次mallocrealloccalloc分配的内存,即使这块内存已经被释放realloc函数仍然可以工作,早起的realloc函数还要求在重新分配内存大小时需事先释放掉这块内存。现在,这种做法并不提倡。

十一,可移植性问题的一个例子

          将long型整数转换为其10进制表示,并对10进制表示中的每个字符调用函数指针所指向的函数:

void printNum(long n, void (*p)(char)) //打印数

{

     if(n < 0)//是否为负

    {

          (*p)('-');//打印负号

          n = -n;  //转换为正数

     }

     if(n > 10)//包含两个以上的数字

          printNum(n/10, p);

      (*p)((int)(n%10) + '0'); //因为在某些机器上intlong的内部表示不同,所以强制转换为int

}

void printChar(char c)

{

        printf("%c",c);

}

问题1

          (*p)((int)(n%10) + '0');在这句中,用+’0’来得到整数对应的字符表示,这种方式的假定前提是机器的字符集中数字是顺序排列的,没有间隔的,这对ASCIIEBCDIC字符集来说是对的,对符合ANSIC实现来说也是对的,但有的机器的字符集并非如此,故可能导致可移植性问题。

解决方法:

           将(*p)((int)(n%10) + '0');替换为(*p)("0123456789"[n % 10]);

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

问题2

if(n < 0)

{

      (*p)('-');

       n = -n;  // 当(n=-2^k)时候发生溢出  

}

             当n为负数时,我们n=-n,这个操作可能引起溢出,因为基于2的补码的计算机允许表示对负数的取值范围要大于正数的取值范围,即如果longk位以及一个符号位,则其表示范围为-2K却不能表示2K,所以为了不出现溢出,要保证不将n转换为对应的正数。可以用一个函数判断正负号,而另一个函数针对负数进行处理,(因为负数的表示范围要大):

void printNum(long n, void (*p)(char))

{

       if(n < 0)

      {

            (*p)('-');

            printNeg(n, p);

       }

      else

           printNeg(-n, p);

}

void printNeg(long n, void (*p)(char))

{

       if(n < -10)

               printNeg(n/10, p);

        (*p)("0123456789"[-(n%10)]);  //注意不要将其写为-n%10,因为这涉及到将n转换为整数

}

问题3

        对于n%10n/10,因为在整数除法和取余运算中,当一个操作数为负数时,其结果与机器的具体实现有关,因此n%10n为负数时有可能是正数,故"0123456789"[-(n%10)会出现错误。因此要检查余数是否在合理的范围内,如果不是则要适当调整两个变量

void printNeg(long n, void (*p)(char))

{

long q = n/10;

int r = n%10;

if(r > 0)

{

r -= 10; //调整余数为负

q++; //调整商

}

if(n < -10)

           printNeg(q, p);

(*p)("0123456789"[-r]);//注意不要将其写为-n%10,因为这涉及到将n转换为整数

}

因此本程序的最终高可移植版本为:

void printNum(long n, void (*p)(char))  //打印函数

{

        if(n < 0)

       {

           (*p)('-');

           printNeg(n, p);

       }

      else

           printNeg(-n, p);

}

void printNeg(long n, void (*p)(char)) //打印负数

{

      long q = n/10;

      int r = n%10;  //判断n 是否为负数时发生溢出

if(r > 0)

{

       r -= 10; //调整余数为负

      q++; //调整商

}

if(n < -10)

          printNeg(q, p);

(*p)("0123456789"[-r]);//注意不要将其写为-n%10,因为这涉及到将n转换为整数

}

void printChar(char c)

{

       printf("%c",c);

}



Exercise7.2

       函数atol的作用是接受一个指向以null结尾的字符串的指针作为参数,返回一个对应的long型的整数值。假定:

       1 作为输入参数的指针指向的字符串总是代表合法的long型整数值,因此atol无需检查输入是否越界;

       2 唯一合法的输入字符是数字和正负号。输入字符串在遇到第一个非法的字符时结束。

请写出atol的一个可移植的版本。

Answer

        这里主要考虑问题是避免中间结果溢出,即使最终结果在取值范围内也是如此。如果将一个负数转换为正数再处理,很有可能发生溢出,下面的程序只是用负数和0来得到函数的结果,从而避免了溢出。

  1. long atol(char* s)  
  2. {  
  3.     long r = 0;  
  4.     int neg = 0;  
  5.     switch(*s) //判断第一个字符是正负号还是其他  
  6.     {  
  7.         case '-':  
  8.             neg = 1;  
  9.         /*此处没有break*/  
  10.         case '+':  
  11.             s++;  
  12.             break;  
  13.     }  
  14.     while(*s >= '0' && *s <= '9')//只有字符在0-9之间时才进行计算  
  15.     {  
  16.         int n = *s++ - '0';  
  17.         if(neg)  
  18.             n = -n;  
  19.         r = r * 10 + n;  
  20.     }  
  21.     return r;  
  22. }  
long atol(char* s)
{
	long r = 0;
	int neg = 0;
	switch(*s) //判断第一个字符是正负号还是其他
	{
		case '-':
			neg = 1;
		/*此处没有break*/
		case '+':
			s++;
			break;
	}
	while(*s >= '0' && *s <= '9')//只有字符在0-9之间时才进行计算
	{
		int n = *s++ - '0';
		if(neg)
			n = -n;
		r = r * 10 + n;
	}
	return r;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值