`
mmdev
  • 浏览: 12894340 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多
一,应对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来得到函数的结果,从而避免了溢出。

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;
}


分享到:
评论

相关推荐

    C陷阱与缺陷-建议与答案

    《C陷阱与缺陷》是人民邮电出版社2008...全书分为8章,分别从词法分析、语法语义、连接、库函数、预处理器、可移植性缺陷等几个方面分析了C编程中可能遇到的问题。最后,作者用一章的篇幅给出了若干具有实用价值的建议

    C陷阱与缺陷 部分笔记

    第一部分研究了当程序被划分为记号时会发生的问题。第二部分继续研究了当程序的记号被编译器组合为声明、表达式和...最后,第七部分讨论了可移植性问题:一个能在一个实现中运行的程序无法在另一个实现中运行的原因。

    C++程序设计陷阱

    它简明扼要地讲述了C程序设计中的陷阱和缺陷,包括词法陷阱、语法陷阱、语义陷阱、连接、库函数、预处理器以及可移植性缺陷等。

    c语言编写单片机技巧

    C语言有功能丰富的库函数、运算速度快、编译效率高、有良好的可移植性,而且可以直接实现对系统硬件的控制。C语言是一种结构化程序设计语言,它支持当前程序设计中广泛采用的由顶向下结构化程序设计技术。此外,...

    LINUX设备驱动第三版_588及代码.rar

    其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb ...

    Linux DeviceDrivers 3rd Edition

    其他有关移植性的问题 291 链表 294 快速参考 298 第十二章 PCI驱动程序 300 PCI接口 300 ISA回顾 317 PC/104和PC/104+ 319 其他的PC总线 319 SBus 320 NuBus 321 外部总线 321 快速参考 322 第十三章 ...

    Windows Sockets网络编程 可能是最清晰版本(Windows Sockets 2规范解释小组负责人亲自执笔。)总共4个包,part1

    1.3.2 Windows Sockets提供源代码可移植性 1.3.3 Windows Sockets支持动态链接 1.3.4 Windows Sockets的优点 1.4 Windows Sockets的前景 1.5 结论 第2章 Windows Sockets的概念 2.1 OSI网络模型 2.2 WinSock网络...

    Windows Sockets网络编程 总计4个包,part2

    1.3.2 Windows Sockets提供源代码可移植性 1.3.3 Windows Sockets支持动态链接 1.3.4 Windows Sockets的优点 1.4 Windows Sockets的前景 1.5 结论 第2章 Windows Sockets的概念 2.1 OSI网络模型 2.2 WinSock网络...

    linux设备驱动程序第三版

    3. 第 2 章 建立和运行模块 .......................................................................................................... 26 3.1 2.1. 设置你的测试系统 .........................................

Global site tag (gtag.js) - Google Analytics