当前位置: 首页 >> 程序设计 >> C语言缺陷与陷阱(笔记)
 

C语言缺陷与陷阱(笔记)

作者:      来源:http://blog.csdn.net/skyyunmi     发表时间:2006-12-28     浏览次数:      字号:    

6.2 宏不是型定
宏的一个通常的用途是保不同地方的多个事物具有相同的型:
#define FOOTYPE struct foo
FOOTYPE a;
FOOTYPE b, c;
程序可以通只改程序中的一行就能改abc型,尽管abc可能声明在很的不同地方。
    使用这样的宏定义还有着可移植性的优势——所有的C编译器都支持它。很多C编译器并不支持另一方法:
typedef struct foo FOOTYPE;
FOOTYPE义为一个与struct foo等价的新型。
    种为类型命名的方法可以是等价的,但typedef更灵活一些。例如,考下面的例子:
#define T1 struct foo *
typedef struct foo * T2;
两个定使得T1T2都等价于一个struct foo的指。但看看当我们试图在一行中声明多于一个量的候会生什
T1 a, b;
T2 c, d;
第一个声明被
struct foo * a, b;
a被定义为一个构指,但b被定义为一个构(而不是指)。相反,第二个声明中cd都被定义为指向构的指,因T2的行好像真正的型一
7 可移植性缺陷
今天,一个C程序如果想写出于不同境中的用都有用的程序就必知道很多微的差
7.1 个名字中都有什
一个标识符是一个字符和数字序列,第一个字符必是一个字母。下划线_算作字母。大写字母和小写字母是不同的。只有前八个字符是名,但可以使用更多的字符。可以被多种汇编器和加器使用的外部标识符,有着更多的限制:
下面著的函数:
char *Malloc(unsigned n) {
    char *p, *malloc();
    p = malloc(n);
    if(p == NULL)
        panic("out of memory");
    return p;
}
    个函数是保耗尽内存而不会致没有检测的一个简单法。程序可以通过调Mallo()来代替malloc()。如果malloc()不幸失,将panic()示一个恰当的错误消息并止程序。
然而,考函数用于一个忽略大小写区的系生什这时,名字mallocMalloc是等价的。话说函数malloc()被上面的Malloc()函数完全取代了,当malloc()用的是它自己。然,其果就是第一次尝试分配内存就会陷入一个递归并随之生混乱。但在一些能区分大小写的实现个函数是可以工作的。
7.2 一个整数有多大?
C程序提供三整数尺寸:普通、短和有字符,其行像一个很小的整数。C言定义对整数的大小不作任何保
1.    整数的四尺寸是非减的。
2.    普通整数的大小要足存放任意的数
3.    字符的大小应该特定硬件的本
代机器具有8位字符,不过还有一些具有79位字符。因此字符通常是789位。
    整数通常至少32位,因此一个整数可以用于表示文件的大小。
    普通整数通常至少16位,因太小的整数会更多地限制一个数的最大大小。
    短整数是恰好16位。
更可移植的做法是定一个新的型:
typedef long tenmil;
在你就可以使用型来声明一个量并知道它的度了,最坏的情况下,你也只要改变这独的型定就可以使所有量具有正确的型。
7.3 字符是符号的是无符号的?
问题在将一个char转换为一个更大的整数时变得尤重要。于相反的转换,其果却是定良好的:多余的位被简单弃掉。但一个编译器将一个char转换为一个int却需要作出选择:将char视为带符号量是无符号量?如果是前者,将charint制符号位;如果是后者,要将多余的位用0填充。
个决定的于那些在理字符时习惯将高位置1的人来非常重要。决定着8位的字符范是从-128127是从0255又影响着程序员对哈希表和转换表之西的设计
    如果你心一个字符最高位置一是否被视为一个数,你应该显式地将它声明unsigned char这样就能保转换为整数是基0的,而不像普通char量那在一些实现中是符号的而在另一些实现中是无符号的。
另外,有一种误解是认为c是一个字符,可以通(unsigned)c来得到与c等价的无符号整数。错误的,因一个char行任何操作(包括转换)之前转换为int这时c会首先转换为一个符号整数再转换为一个无符号整数,生奇怪的果。
    正确的方法是写(unsigned char)c
7.4 右移位是符号的是无符号的?
里再一次重:一个心右移操作如何行的程序最好将所有待移位的量声明无符号的。
7.5 除法如何舍入?
ba得到商q余数r
q = a / b;
r = a % b;
们暂时b > 0
1.    最重要的,我期望q * b + r == a,因为这余数的定
2.    如果a的符号生改,我期望q的符号也生改,但绝对值
3.    希望保r >= 0r < b。例如,如果余数将作一个哈希表的索引,它必要保证总是一个有效的索引。
    三点清楚地描述了整数除法和求余操作。不幸的是,它不能同时为真。
3 / 2,商10(1)这满足第一点。而-3 / 2呢?根据第二点,商应该-1,但如果是这样,余数必也是-1这违反了第三点。或者,我可以通将余数标记为1足第三点,但这时根据第一点商应该-2反了第二点。
因此C和其他任何实现了整数除法舍入的言必放弃上述三个原中的至少一个。
很多程序设计语言放弃了第三点,要求余数的符号必和被除数相同。可以保第一点和第二点。很多C实现也是这样做的。
 尽管有些候不需要灵活性,C是足可以令除法完成我所要做的、提供我所想知道的。例如,假有一个数n表示一个标识符中的字符的一些函数,并且我想通除法得到一个哈希表入口h,其中0 <= h <= HASHSIZE。如果我知道n是非的,我可以简单地写:
h = n % HASHSIZE;
然而,如果n有可能是的,这样写就不好了,因h可能也是的。然而,我知道h > -HASHSIZE,因此我可以写:
h = n % HASHSIZE;
if(n < 0)
    h += HASHSIZE;
    ,将n声明unsigned也可以。
7.6 一个随机数有多大?
个尺寸是模糊的,库设计影响。在PDP-11[10]机器上运行的有的C实现中,有一个称rand()的函数可以返回一个()随机非整数。PDP-11中整数度包括符号位是16位,因此rand()返回一个0215-1的整数。
    CVAX-11实现时,整数的变为32。那VAX-11上的rand()函数返回是什呢?
    个系,加利福尼大学的认为rand()的返回值应该涵盖所有可能的非整数,因此它rand()版本返回一个0231-1的整数。
    AT&T的人则觉得如果rand()函数仍然返回一个0215可以很容易地将PDP-11中期望rand()返回一个小于215的程序移植到VAX-11上。
    因此,写出不依赖实现rand()函数的程序。
7.7 大小写转换
toupper()tolower()函数有着似的史。他最初都被实现为宏:
#define toupper(c) ((c) + 'A' - 'a')
#define tolower(c) ((c) + 'A' - 'a')
些宏确有一个缺陷,即:当定的西不是一个恰当的字符,它会返回垃圾。因此,下面个通使用些宏来将一个文件转为小写的程序是无法工作的:
int c;
while((c = getchar()) !=
EOF)
    putchar(tolower(c));
写:
int c;
while((c = getchar()) != EOF)
    putchar(isupper(c) ? tolower(c) : c);
一点,AT&T中的UNIX开发组织提醒我toupper()tolower()都是事先经过一些适当的参数测试的。考虑这样重写些宏:
#define toupper(c) ((c) >= 'a' && (c) <= 'z' ? (c) + 'A' - 'a' : (c))
#define tolower(c) ((c) >= 'A' && (c) <= 'Z' ?
(c) + 'a' - 'A' : (c))
但要知道,c的三次出都要被求会破坏如toupper(*p++)这样的表达式。因此,可以考toupper()tolower()重写函数。toupper()看起来可能像这样
int toupper(int c) {
    if(c >= 'a' && c <= 'z')
        return c + 'A' - 'a';
    return c;
}
tolower()似。
    个改变带来更多的问题次使用些函数的候都会引入函数开销。我的英雄认为一些人可能不愿意支付开销,因此他个宏重命名
#define _toupper(c) ((c) + 'A' - 'a')
#define _tolower(c) ((c) + 'a' - 'A')
就允户选择方便或速度。
    里面其只有一个问题:伯克利的人和其他的C实现者并没有跟着这么做。意味着一个在AT&T写的使用了toupper()tolower()的程序,如果没有传递正确大小写字母参数,在其他C实现中可能不会正常工作。
    如果不知道史,可能很难对这类错误进行跟踪。
7.8 放,再重新分配
很多C实现为提供了三个内存分配函数:malloc()realloc()free()malloc(n)返回一个指向有n个字符的新分配的内存的指个指可以由程序使用。free()传递一个指向由malloc()分配的内存的指可以使这块内存得以再次使用。通一个指向已分配区域的指和一个新的大小realloc()可以将这块内存大或小到新尺寸,程中可能要制内存。
    有人会想,真相真是有点微妙啊。下面是System V接口定中出realloc()的描述:
realloc一个由ptr指向的size个字,并返回该块(可能被移)的指在新旧尺寸中比小的一个尺寸之下的内容不会被改此外,包含了描述realloc()的另外一段:
如果在最后一次mallocrealloccalloc放了ptr所指向的realloc依旧可以工作;因此,freemallocrealloc序可以利用malloc压缩找策略。
因此,下面的代片段在UNIX第七版中是合法的:
free (p);
p = realloc(p, newsize);
    一特性保留在从UNIX第七版衍生出来的系中:可以先放一区域,然后再重新分配它。意味着,在些系放的内存中的内容在下一次内存分配之前可以保。因此,在些系中,我可以用下面这种奇特的思想来放一个表中的所有元素:
for(p = head; p != NULL; p = p->next)
    free((char *)p);
而不用担心free()p->next不可用。
不用这种是不推荐的,因不是所有C实现都能在内存被放后将它的内容保留足够长时间。然而,第七版的手册留了一个未声明的问题realloc()的原始实现实际上是必要先放再重新分配的。出于个原因,一些C程序都是先放内存再重新分配的,而当些程序移植到其他实现就会出现问题
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'是相同的,等等。尽管个假设对ASCIIEBCDIC字符集是成立的,但于其他一些机器可能不成立。避免问题的方法是使用一个表:
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 / 10n % 10n的前数字和尾数字(经过适当的符号变换)。用整数除法的行在其中一个操作数为负候是实现的。因此,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]);
}
8 里是空
参考
    The C Programming Language》(Kernighan and Ritchie, Prentice-Hall 1978)是最具威的C著作。它包含了一个秀的教程,面向那些熟悉其他高级语言程序设计的人,和一个参考手册,简洁地描述了整个言。尽管自1978年以来这门语生了不少化,书对于很多主仍然是个定时还包含了本文中多次提到的“C言参考手册
    The C Puzzle Book》(Feuer, Prentice-Hall, 1982)是一本少的磨文法能力的收集了很多谜题(和答案),它的解决方法能够测试读C言精妙之的知
    C: A Referenct Manual》(Harbison and Steele, Prentice Hall 1984)是特意为实现写的一本参考料。其他人也会发现它是特有用的——他能从中参考细节
1.是基于图书C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一个充,有趣的者可以它。 

[1] [2] [3]

责任编辑 webmaster

 
发表评论  打印本文  推荐本文  加入收藏  返回顶部  关闭窗口
 
 
 
 
评论更多>>
 
希望能继续写出这样的文章! 确实 简直就是我们初学者的福音啊
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •