首页 Author 王聪

Author 王聪

举报
开通vip

Author 王聪Author 王聪 "Clearly your code does not meet the original spec." "You are sentenced to 30 lashes with a wet noodle." -- Jerry Coffin in a.l.c.c C语言中的确有相当多的值得我们注意的地方,《C Traps and Pitfalls》一书中已有详细的论述。我自己在学习C语言中还遇到了一些其他的问题,我认为同样值得引起重视。在 这里写出来,与大家讨论一下。 初学者有不少会和...

Author 王聪
Author 王聪 "Clearly your code does not meet the original spec." "You are sentenced to 30 lashes with a wet noodle." -- Jerry Coffin in a.l.c.c C语言中的确有相当多的值得我们注意的地方,《C Traps and Pitfalls》一书中已有详细的论述。我自己在学习C语言中还遇到了一些其他的问 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 ,我认为同样值得引起重视。在 这里写出来,与大家讨论一下。 初学者有不少会和我当初一样认为在#include命令中<>和""是等价的,如果你还那样认为那就大错特错了!其实在#include命令中,头文件名称两侧如果是<>则说明头文件及其安装在硬盘中编译程序的 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 库区域;而如果是""则说明头文件保存在程序员自己的当前的磁 盘目录中,而不是在标准系统目录中的本地库;如果在当前目录中找不到,再按尖括号包围 时的办法重新搜索一次。这点应该引起初学者足够的重视,并不是所有的 C语言教程中都会明确指出这一点,而忽视它的后果将是编译器找不到头文件的错误提示。 sizeof不是函数,而是一元操作符;return也不是函数,而只是关键字。如果计算的是 一种类型的大小,sizeof就需要加一对圆括号,但这并不是说它是一个函数。return不是函数,不需要加圆括号。 0.0,0,\0,NULL都是完全由0比特组成,但是它们的长度不同。0.0是双精度,通常是8个字节;\0是字符常量,只有1个字节;NULL表示空指针,它的长度由系统 决定 郑伟家庭教育讲座全集个人独资股东决定成立安全领导小组关于成立临时党支部关于注销分公司决定 ,也就 是系统中储存一个内存地址所需的字节数;0表示整数,在gcc中是4个字节。它们的类型也不同,在用它们给变量赋值时应引起注意,否则就会得到编译错误。 在用fgets()读文件时,我们会用EOF判断文件的结束。所以很多人认为EOF也是文件的一部分,用它来表示文件结束。但实际上这是错误的,文件数据中并没有EOF之类的文件结束符!在stdio.h中你会发现有这么一段宏: #define EOF –1 对,EOF就是-1!对getc()而言,如果不能从文件中读取,则返回一个整数 -1,这就是所谓的EOF。为什么EOF是-1呢?因为在正常读取的情况下, 返回的整数均小于255, 即0x00~0xFE. 而读不出返回的是0xFF,根据补码知识,-1和255是一样的。0xFF会让我们引起混淆,当返回值不是原型int时!因为int里的EOF是0xFFFFFFFF(gcc2/x86上int是32位)。当定义成其它类型时会进行相应的转换,就会引起混淆!如果你在不同平台上移植程 序时,一定要小心它。 多年前,匈牙利程序员Charles Simonyi 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 了一种在变量名中加上特定的前缀来辨别 变量类型的命名方法,它的优点很显然,就是可以直接通过变量名来辨认其类型,而不用去 查找它的定义。微软后来才用了这种思想,我们可以在vc中大量看到。我在学vc时 对这里特别头疼,它不仅使变量的名字很古怪,很绕口,而且还有个很大的缺点,那就是可能会 使改变变量类型的工作变得十分艰巨。设想一下,我们在一个大型项 目中定义了一个全局变量,经过几十多个函数使用后,我们突然发现这种类型的字节数不够用,我们不得不去改 变它的类型,好了,你得从头开始把它的名字都得改 一遍!好象现在很多程序员都放弃了 它,除了一些顽固的Windows程序员,但是它在某些场合仍然存在!我们仍能在使用指针变量时习惯加上p前缀。这在小型程序中完全可以,改变并不费力,而且还形象,但在大型程 序中应该避免。 到底calloc()分配内存成功时到底做了一些什么,相信很多人和我以前一样,认为它为 数组分配了块内存,而且还把其所有数组元素初始化为0。这也难怪,因为很多c语言教程中都是那么讲的。可是calloc()真地像我们想像的那样做了吗?答案是不确定的。看看你手边 的ANSI C99(Page312 7.20.3.1节)吧,你是否注意到calloc()描述中最后一句---The space is initialized to all bits zero.后面的note了呢?note是这样的:Note that this need not be the same as the representation of floating-point zero or a null pointer constant.好了,这就对了。“c语言不保证所有位为0是指针(null)还是浮点数(0.0)的零表示!如果可移植性对你真的很重要,那么还 是请你别倚赖calloc()把变量初始化为0;如果数组元素是指针或浮点数(或者你正创建包含浮 点数或指针的结构体或联合数组),则可以利用循环自己进行初始化。”初始化真的很重要, 尤其是你对可移植性要求高时,指针是否初始化为null,数组元素是否都被初始化等很多时候都决定了程序的成败,而有时查出这些错误又是那么的困难。我们唯一要做的就是格外小 心。 ANSI C99中第7.19.6.6节关于sprintf()的描述: “#include int sprintf(char * restrict s,const char * restrict format,...); sprintf函数等价于fprintf,除了输出是写入一个数组(记为参数s)而不是一个流。null字符被 写入所写字符组的结尾;它不被计入返回值。如果复制发生在重叠的对象之间,行为则是未 定义的。” 这倒是没什么奇怪的,可是有人曾想利用sprintf()给一个字符数添加不同类型的值: sprintf(mystring,"%s%d%s%f",string,j,otherstring,d); 想法不错,但很可惜,它并不能真正地将其它类型加入字符数组中,而是统统将它们直接而 非强制转化成字符后又加入的。下面的程序证明了我的猜测: char tempstring[50]={0}; char* otherstring; int d=5; float j=3.14; otherstring="other"; sprintf(tempstring,"%f%s%d",j,otherstring,d); printf("%s\n",tempstring); printf("%d\n",tempstring[13]); 当然你也许会说我们仍可以把它当混合数组使用,但需要一些技巧。sprintf()或许是把其它类型转化成字符型不错的函数。我们应当学会合理地利用sprintf()。 看下面的程序: #include int main(void){ int t; char c; do{ scanf("%d",&t); scanf("%c",&c); printf("t=%d c=%c\n",t,c); #if 0 printf("c=%d\n",c); #endif }while(c!='N'); return 0; } 运行一下就知道scanf()并不能正常地接受字符,为什么?因为我们结束输入时按了Enter键。如果我们把#if 0改成#if 1启用第二个printf()看看,显示c=10,10对应的ASCII码是\n即换行。这就很好解释了。我们按下Enter时会向键盘缓冲区发出一个\n和一个\r(键盘输入实际上通过两个缓冲区),\n被scanf()错误地传递给c,\r被正好scanf()当做结束符。解决方法是使用fflush()函数。ANSI C99中关于这方面的陈述如下(7.19.5.2节): “#include int fflush(FILE *stream); 如果流指向一个输出流或最近没有进行输入操作的更新流,那么fflush()函数就把任何没有写入流的数据发送到主环境并写入该文件;否则,状态就是不确定的。” 所以我们可以在两个scanf()之间加入fflush(stdin) ;但我们最好不要使用scanf()交互地得到字符数据,因为scanf()更倾向于从stdin得到格式化的数据,从操作者那里显然不能很好地 直接得到格式化的数据。建议是试一试fgets()吧。 gets()函数的恶名早就通过臭名昭著的Internet Worm传开了,因为Worm就是基于gets()攻击的。现在的内存检测程序都会把gets()给检测出来,作为漏洞报告。有人称它是“一个 等待发生的错误”并非没道理的,它永远不可能得到足够的内存而保证它安全使用,它导致 的后果轻则使程序出错,重则使操作系统出现漏洞被黑客利用。所以当用户输入5个以上的字符时,下面的程序会陷入大的麻烦。 int main(void){ int a=8; char s[5]; int b=7; gets(s); printf("a=%d,s=%s,b=%d",a,s,b); return 0; } 在基于栈的系统上,黑客可以相当容易地赋给gets()一个精心构造的可以改写栈中返回地址 的字符串,从而使指针指向内存中任意位置,这会让黑客为所欲为!我们所要做的就是永远 都不要使用gets()。接受字符串时,可以小心地使用scanf(),但fgets()会更好。 strtok()函数对初学者真的不好用,在ANSI C99中关于此函数的描述长达6条。如果你不清楚它的用法,请参阅ANSI C99中第7.21.5.8节。下面主要谈谈关于它的细节。strtok()函数拥有一块与之相关的静态数据——如果传递给它一个空指针,它将“不断搜索”。(你不 妨试着编写自己的strtok()函数。)这提醒我们如果在一个递归函数中使用strtok()应该小心了,因为带有静态数据的对象可能不能与递归函数默契合作。这个函数也没有为我们提供抵 达其内部缓冲区的方式。如果你真对此有要求的话,不妨试试扩展函数strsep(),但是它不是在每个系统中都有。它与strtok()功能相似,但它没有使用静态缓冲区。strtok()可以毫不含糊地将每一行分解为单个字段,并且没有空字段,但也会使代码不可重入。建议是使用它 之前仔细阅读strtok()文档。在环境适宜时,它仍不失为一个优秀的工具。 ” 标准只要求qsort()函 数排列数据,并没有要求它必须是快速排序算法的一个实现。编 译器生产商可以有自己的实现,这我们就难以知晓了。即使是他们真的是使用了快速排序算 法的一种 流行的实现,那么快速排序算法的缺点也是很显然的。“对于相当无序的数据,使 用快速排序算法可得到很好的效果;但对于许多普通输入,它的表现易于反常。存 在可引起退化到二次行为的严重缺陷。”如果你真的对排序要求苛刻,就不要使用qsort()函数。建议是编写自己的排序函数或从www上down一些优秀的排序代码。 初学者可能会和我当初一样奇怪,明明scanf()的原型是int,也就是说它应该返回一个 整数值。但是我们通常都直接使用: „„ scanf(...); „„ 不只是int,一些char*函数也如此,比如: long ltemp; char buffer[9]; „„ ltoa(ltemp,buffer,10); „„ 就像void函数那样,为什么呢?我询问了comp.lang.c的专家们,得到的解释是:那是因为你想那样用。有一些语言禁止放弃函数的返回值,C不是它们中的一员。也有一些程序员把上面的函数写成: (void)printf(“hello world!\n”); (void)scanf(“%d”,&value); 来说明他们放弃了函数的返回值。你使用非void类型的函数时,你没必要非得使用它的返回值,但是大多数情况下,你应该用,那些值可能很有用。 int函数在C中很特殊,ISO C早期并没有void类型,如果你在函数标题中的返回类型字段保持空白,那么返回类型缺省为int,这是ISO C为了维持与旧版本之间的兼容性。这一点应该引起足够重视,因为如果我们遗漏原型或原型在第一次函数调用后出现,编译器可能使 用第一次函数调用中的形参类型构建一个原型,返回类型永远是int!这可能对,当然也可 能不对。 “结构和联合中允许有定义为位字段(bit-field)的成员。位字段可以定义为int,signed int, unsigned int之一,并且可以给出附加的宽度值。”下面我们将讨论位字段,需要声明的一点 是:“计算机体系结构并不是完全标准化的,在字节层次上处理存储器显然要涉及很多硬件 方面的非标准的问题”也就是说,下面的程序在不同的编译器,不同的机器上运行结果很可 能是不同的。在《Applied C:An Introduction and More》一书中提到如下程序,它可以确定你的硬件系统和编译器处理位字段的方式: #include typedef struct DEMO{ unsigned int one:1; unsigned int two:3; unsigned int three:10; unsigned int four:5; unsigned int :2; unsigned int five:8; unsigned int six:8; }demo_type; int main(void){ int k; unsigned char* bptr; demo_type bit={1,5,513,17,129,0x81}; printf("\n sizeof demo_type =%lu\n",sizeof(demo_type)); printf("initial values:bit=%u,%u,%u,%u,%u,%u\n",bit.one,bit.two,bit.three,bit.four,bit.five,bit.six); bptr=(unsigned char *)&bit; printf("hex dump of bit: %02x %02x %02x %02x %02x %02x \n",bptr[0],bptr[1],bptr[2],bptr[3],bptr[4],bptr[5]); bit.three=1023; printf("\n assign 1023 to bit.three: %u,%u,%u,%u,%u,%u\n",bit.one,bit.two ,bit.three,bit.four,bit.five,bit.six); k=bit.two; printf("assign bit.two to k:k=%i\n",k); return 0; } 在我机器上(gcc version 2.96 20000731 (Red Hat Linux 7.3 2.96-110))运行结果如下: sizeof demo_type =8 initial values:bit=1,5,513,17,129,129 hex dump of bit: 1b 60 24 10 81 00 assign 1023 to bit.three: 1,5,1023,17,129,129 assign bit.two to k:k=5 在我的机器上,demo_type是8字节,而上面的程序只打印出6个字节,所以我还要把最后两 个字节打印出来,我修改了第3个printf()语句,改成: printf("hex dump of bit: %02x %02x %02x %02x %02x %02x %02x %02x \n",bptr[0],bptr[1],bptr[2],bptr[3],bptr[4],bptr[5],bptr[6],bptr[7]); 好了,这样就打印出全部了,结果是: hex dump of bit: 1b 60 24 10 81 00 00 00 我对以上结果进行解释: gcc上的int是4位的,编译器分配内存时以4字节为单位分配,所以分配了两个单位即8 个字节。在储存位字段时,编译器是从右向左分配的。这也是编译器分配位字段的最简单的 方式。这当然会因机器不同而异,我在另一台Windows机器上却得到了:sizeof demo_type =147914754 的惊人数字!。C语言中,关于编译器如何安排位字段的 规定 关于下班后关闭电源的规定党章中关于入党时间的规定公务员考核规定下载规定办法文件下载宁波关于闷顶的规定 很少。确实存在某 些种类的分配单元,而且分配单元大小也取决于编译器,但编译器可以从高位或低位开始分 配位字段。还有一些注意事项,具体请参考《The C Programming Language》第150页。总 之使用位字段时应该格外小心。 .: structure 可以很好的把相关的数据保存在一起,但是C语言并没有要求编译器在内存 中连续地排列结构体成员。编译器在结构体的成员之间或结构体的结尾(但不能是开头)插 入填充位是合法的。编译器不仅允许这样,而且它的确是这样做的。所以下面的代码: struct FOO { char a; char b; char c; char d; char e; char f; }; FOO foo={0}; char *p; char q[]="hello"; int i; p=(char *)&foo; for(p=&foo.a,i=0;q[i]!='\0';i++,p++) *p=q[i]; printf("%s\n",&foo.a); 这相当可怕,我们并不能确保它打印出"hello"! segmentation fault是我们编写C程序时很常见的错误,尤其是在Linux上。 然而很多人只是知道它与指针有关,却不知道引发错误的细节。我想在这里简单地说说我的理解。我 们知道操作系统管理虚拟内存有两种方式:一种是分段,另一种 是分页。分页的意思是内存被分成相等大小的页面来管理,每个页中包含有内存的字。分段的意思是每个进程都有一 个所需大小的内存段,段与段之间以空白的存储 块相间隔。操作系统知道每一段的上界, 并且每个段都以虚拟0地址开始。当程序访问一个内存块时,它调用的是虚拟地址,但MMU会把它映射到一个真实的地址。如果操作系统发现请求地址与有效的段地址都不匹配,它就 会向进程发送一个终止信号。segmentation fault也就这样产生了。如果segmentation fault发生,说明你的程序中有错误指针,内存泄露,或者其它访问错误内存地址的错误。请你再仔 细地检查你的指针,数组是否正确,一般是使用malloc()分配了多少内存就使用多少,不使 用未分配内存的指针进行strcpy()等操作。想了解更多请参阅《Expert C Programming》。 这只是我自己总结的,当然还远不只这些。尽管我非常谨慎,但仍难免有出错的地方希 望大家补充和指正。我的email是:ccode@126.com。 参考资料: nd《The C Programming Language 2 Ed》 《C Unleashed》 《Applied C:An Introduction and More》 th《C : A Reference Manual 4 Ed》 《ANSI C99》 Trackback:
本文档为【Author 王聪】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_977556
暂无简介~
格式:doc
大小:31KB
软件:Word
页数:0
分类:互联网
上传时间:2017-12-13
浏览量:5