7.9 可移植性问题的一个实例
让我们来看一个已经被很多人在很多时候解决了的问题。下面的程序带有两个参数:一个长整数和一个函数(的指针)。它将整数转换位十进制数,并用代表其中每一个数字的字符来调用给定的函数。
void printnum(long n, void (*p)()) {
if(n < 0) {
(*p)('-');
n = -n;
}
if(n >= 10)
printnum(n/ 10, p);
(*p)(n % 10 +'0');
}
这个程序非常简单。首先检查n是否为负数;如果是,则打印一个符号并将n变为正数。接下来,测试是否n >= 10。如果是,则它的十进制表示中包含两个或更多个数字,因此我们递归地调用printnum()来打印除最后一个数字外的所有数字。最后,我们打印最后一个数字。
这个程序——由于它的简单——具有很多可移植性问题。首先是将n的低位数字转换成字符形式的方法。用n % 10来获取低位数字的值是好的,但为它加上'0'来获得相应的字符表示就不好了。这个加法假设机器中顺序的数字所对应的字符数顺序的,没有间隔,因此'0' + 5和'5'的值是相同的,等等。尽管这个假设对于ASCII和EBCDIC字符集是成立的,但对于其他一些机器可能不成立。避免这个问题的方法是使用一个表:
void printnum(long n, void (*p)()) {
if(n < 0) {
(*p)('-');
n = -n;
}
if(n >= 10)
printnum(n/ 10, p);
(*p)("0123456789"[n % 10]);
}
另一个问题发生在当n< 0时。这时程序会打印一个负号并将n设置为-n。这个赋值会发生溢出,因为在使用2的补码的机器上通常能够表示的负数比正数要多。例如,一个(长)整数有k位和一个附加位表示符号,则-2k可以表示而2k却不能。
解决这一问题有很多方法。最直观的一种是将n赋给一个unsigned long值。然而,一些C便一起可能没有实现unsigned long,因此我们来看看没有它怎么办。
在第一个实现和第二个实现的机器上,改变一个正整数的符号保证不会发生溢出。问题仅出在改变一个负数的符号时。因此,我们可以通过避免将n变为正数来避免这个问题。
当然,一旦我们打印了负数的符号,我们就能够将负数和正数视为是一样的。下面的方法就强制在打印符号之后n为负数,并且用负数值完成我们所有的算法。如果我们这么做,我们就必须保证程序中打印符号的部分只执行一次;一个简单的方法是将这个程序划分为两个函数:
void printnum(long n, void (*p)()) {
if(n < 0) {
(*p)('-');
printneg(n,p);
}
else
printneg(-n, p);
}
void printneg(long n, void (*p)()) {
if(n <= -10)
printneg(n/ 10, p);
(*p)("0123456789"[-(n % 10)]);
}
printnum()现在只检查要打印的数是否为负数;如果是的话则打印一个符号。否则,它以n的负绝对值来调用printneg()。我们同时改变了printneg()的函数体来适应n永远是负数或零这一事实。
我们得到什么?我们使用n /10和n % 10来获取n的前导数字和结尾数字(经过适当的符号变换)。调用整数除法的行为在其中一个操作数为负的时候是实现相关的。因此,n % 10有可能是正的!这时,-(n % 10)是负数,将会超出我们的数字字符数组的末尾。
为了解决这一问题,我们建立两个临时变量来存放商和余数。作完除法后,我们检查余数是否在正确的范围内,如果不是的话则调整这两个变量。printnum()没有改变,因此我们只列出printneg():
void printneg(long n, void (*p)()) {
long q;
int r;
if(r > 0) {
r -= 10;
q++;
}
if(n <= -10){
printneg(q,p);
}
(*p)("0123456789"[-r]);
}
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。