优质C程序秘诀---第 7 章 - 编码中的假象

上传人:s9****2 文档编号:557286024 上传时间:2024-03-10 格式:DOC 页数:17 大小:79.50KB
返回 下载 相关 举报
优质C程序秘诀---第 7 章 - 编码中的假象_第1页
第1页 / 共17页
优质C程序秘诀---第 7 章 - 编码中的假象_第2页
第2页 / 共17页
优质C程序秘诀---第 7 章 - 编码中的假象_第3页
第3页 / 共17页
优质C程序秘诀---第 7 章 - 编码中的假象_第4页
第4页 / 共17页
优质C程序秘诀---第 7 章 - 编码中的假象_第5页
第5页 / 共17页
点击查看更多>>
资源描述

《优质C程序秘诀---第 7 章 - 编码中的假象》由会员分享,可在线阅读,更多相关《优质C程序秘诀---第 7 章 - 编码中的假象(17页珍藏版)》请在金锄头文库上搜索。

1、第7章 编码中的假象写小说,就希望每一页都能吸引读者,使读者激动、吃惊、悬念!决不能使读者感到厌烦。因此在每一页都要撒些胡椒粉,描述一些场景来吸引读者、如果小说写成;“罪犯走近乔并刺伤了他”,读者就会睡觉了。为了使读者感兴趣,就要使得当描述到乔听到身后“咚!咚!咚!”的脚步声时,读者也能感觉到乔是怎样的恐惧;当“咚!咚”的脚步声慢慢地越来越近的时候,读者也能感觉到乔的手在冒汗;当脚步声加速,罪犯朝乔逼近的时候,读者也能理解到乔是怎样的惊慌。最重要的是读者保持着悬念,乔能不能逃脱?在小说中使用惊奇和悬念很重要也很必要。但是如果把它们放到代码中,那就糟糕了。当写代码时,“情节”应该直观,以便别的程

2、序员能预先清楚地知道将要发生的一切。如果用代码表述罪犯走近乔并刺伤了他,那么写成“罪犯走近乔并刺伤了他”最恰当了。该代码简短、清楚、并讲述了所发生的一切。但是由于某些原因,程序员拒绝写简捷清楚的代码,却极力主张使用具有技巧的、比较精炼的、异乎寻常的编码方法,最好不要这样。但是直观的代码并不意味着是简单的代码,直观的代码可以使你沿着一条明确无奇的路径从A点到达B点。必要的时候直观的代码可能也很复杂。因此,本章将考察导致产生不直观代码的编程风格。例子都很巧妙、有技巧,但是并非显而易见,当然,这些程序都会引起一些微妙的错误。要注意到底引用了什么下面的代码是上一章所给的memchr的无错版本:void

3、 *memchr(void *pv, unsigned char ch, size_t size)unsigned char *pch = (unsigned char *)pv;while(size- 0)if(*pcd = ch)return(pch);pch+;return(NULL);大多数程序员玩弄的一种游戏是“我如何使得代码更快?”的游戏。这并不是坏游戏,但是正如我们从这本书所感到的那样:如果过份地热衷于这种游戏,那就是坏事。例如如果在上面的例子上玩这个游戏的话,你就会问自己:“如何使循环加快?”只有三种可能的途径:删除范围检查、删除字符测试、或删除指针递增,好象删除哪一步骤都不行

4、,但是如果愿意放弃传统的编码方式并进行大胆尝试的话是可以删除的。看一下范围检查,之所以需要该检查仅仅是因为:当在存储器的头size个字节内没有找到要找的字符ch时,就要返回NULL。要删除该检查,只要简单地保证总可以找到ch字符就可以了。这可以通过下面的方法来实现:在被查找的存储区域后面的第一个字节上存放字符ch。这样,若待查存储区域内无字符此时,就可以找到后存入的这个ch字符:void* memchr(void *pv, unsigned char ch, size_t size)unsigned char *pch = (unsigned char *)pv;unsigned char *

5、pchPlant;unsigned char chSave;/* pchPlant指向要被查寻的存储区域后面的第一个字节 * 将ch存储在pchPlant所指的字节内来保证memchr肯定能挂到Ch */pchPlant = pch+size;chSave = *pchPlant;*pchPlant = ch;while(*pch != ch)pch+;*pchPlant = chSave;return(pch = pchPlant)?NULL : pch);巧妙吗?正确吗?通过用ch覆盖pchPlant指向的字符,可以保证memchr总能找到ch,这样就可以删除范围检查,使循环的速度加倍。但

6、是,这样坚挺、可靠吗?这个memchr的新版看上去似乎坚挺,特别是它还仔细地把pchPlant原来所指的要被覆盖的字符保存起来,但是memchr的这个版本还是有问题。对于初学者来讲,请考虑下面几点:l 如果pcPlant指向只读存储器,那么在*pchPlant处存放字符ch就不起作用,因此当在size+1范围内没有发现ch时,函数将返回无效指针。l 如果pchPlant指向被映射到I/O的存储器,那么将ch存储在*pchPlant处就难以预计会发生什么事情,从使得软盘停止(或开始)工作到工业机器人狂暴地挥舞焊枪都有可能。l 如果pch指向RAM最后的size个字节,pch和size都是合法的,

7、但pchPlant将指向不存在的或是写保护的存储空间。将ch存储在*pchPlant处就可能会引起存储故障,或是不做任何动作。此时如果在size+1个字符内没有找到宇符ch,函数就会失败。l 如果pchPlant指向的是并行进程共享的数据,那么当一个进程在*pchPlant处存储ch时,就可能错改另一个进程要引用的存储空间。最后一点尤其会引起麻烦,因为有许多方式都可以引起系统瘫痪。如果你调用memchr来查寻已分配了的存储空间,却不料破坏了存储管理程序的某个数据结构,这将如何是好呢?如果并行进程是代码连接或中断处理之类的例程,那么最好不要调用存储管理程序,否则系统可能会瘫痪。如果调用memch

8、r扫描全局数组并且步入了由另一个任务引用的交界变量,那又该如何呢?如果程序的两个实例要并行地查找共享数据时,那又会怎样呢?有很多情况都会使程序死掉。当然,你还不能体验到memchr引起的微妙错误,因为只要不修改关键的存储区,它就会工作得很好。但像memchr这样的函数一旦引起了错误,要孤立这些错误就象在大海里捞针一样的困难。这是因为:执行memchr的进程工作得很好,而另一个进程却因为存储区损坏而崩溃,此时,就没有理由怀疑是memchr引起的。这样错误就很难发现。现在你就知道了,为什么要买价值$50,000的电路仿真器了。因为它们记录从开始到崩溃前的每一个周期、每一条指令、和计算机引用的每一段

9、数据。可能要花几天时间才能艰难地读完仿真器的输出,但是如果坚持而且不盲目地处理这些输出结果的话,应该能找到错误之所在。早已有警句:不要引用不属于你的存储区。我们又何必如上例那样忍受痛苦绞尽脑汁呢?注意,“引用”意味着不仅要读而且要写。读未知的存储区可能不会和别的进程产生不可思议的相互作用,但是,如果引用了已保护的存储区、不存在的存储区、或者映射到I/O存储区的话,程序将会迅速死掉。只引用属于你自己的存储空间拿车钥匙的贼还是赋很奇怪有些程序员,他们从不引用不属于地们自己的存储空间。但他们却觉得编写象下面FreeWindowsTree例程这样的代码是很正确的:void FreeWindowsTre

10、e(windows *pwndRoot)if(pwndRoot != NULL)window *pwnd;/* 释放pwndRoot的子窗口 */for(pwnd = pwndRoot-pwndChild;pwnd != NULL;pwnd = pwnd-pwndSibling)FreeWindowTree(pwnd);if(pwndRoot-strWndTitle != NULL)FreeMemory(pwndRoot-strWndTitle);FreeMemory(pwndRoot);请看一下for循环,看出什么问题了吗?当FreeWindowsTree释放pwndSibling链表中的每

11、个子窗口时,先释放pwnd,然后for循环在控制赋值时又引用已释放的块:pwnd = pwnd-pwndSilbing;但是一旦pwnd被释放,那么pwnd-pwndSibling的值是什么呢?当然是一堆垃圾。但是某些程序员并不接受这个事实,刚刚存储区还好好的,并且也没做什么影响它的事,它仍应该是有效的呀!也就是说,除了释放pwnd之外没做别的什么事情。我从不明白为什么某些程序员会认为引用已经释放的存储区是允许的,这与你使用过去的钥匙进入曾经住过的公寓或开走曾经属于你的汽车又有什么区别呢?你之所以不能安全引用释放的存储空间是因为正如我们在第3章中讲的,存储管理程序可能已将这块释放的空间连到空闲

12、链上了,或已将它用于别的私有信息了。只有系统才能拥有空闲的存储区,程序员不能拥有数据的权限在你所阅读的程序设计手册中可能没有讲到这个问题,但是,在代码中的每一条数据都隐含地有一个与之相联系的读写权限。该权限没有明文出处,也没在声明变量时显式地给出,而是在设计子系统和函数的界面时隐含地声明的。例如,实际上在调用某个函数的程序员和写这个函教的程序员之间有个隐式的约定:假设我是调用者,你是被调用者,如果我向你传递一个指向输入的指针,那么你就同意将输入当作常量并且承诺不对其进行写操作。同样,如果我向你传递一个指向输出的指针,你就同意把它当作只写对象来处理并承诺不对其进行读操作。最后,无论指针指向输入还

13、是指向输出,你都同意严格限制对保存这些输出的存储空间的引用。回过来说,我这个调用者同意把只读输出当作常量并已承诺不对它们进行写操作。此外,还同意严格限制对保存这些输出空间的引用。换句话说:“你不要搞乱我的事情,我也不搞乱你的事情。”要牢记:任何时候,只要你违反了隐含的读写权限,那么就冒着中断代码的危险,因为编写这些代码的程序员坚信每个程序员都应遵守这些约定。调用象memchr这样的函数程序员不应担心在一些特殊的情况下,memchr会运转异常。仅取所需上一章,我们给出了UnsToStr函数的一种实现方法,它如下所示:/* UnsToStr一将无符号值转换为字符串 */void UnsToStr(

14、unsigned u,char *str)char *strStart = str;do*str+ = (u % 10) + 0;while(u/=10)0);*str = 0;ReverseStr(strStart);上面的代码是UnsToStr的直接实现,但是,有些程序员觉得这样做法不舒服,因为代码以反向顺序导出数字,却要建立正向顺序的字符串。因此需要调用ReverseStr来重排数字的顺序。这样似乎很浪费。如果你打算以反向顺序导出数字,为什么不建立反向顺序的字符串从而可以取消对ReverseStr的调用呢?为什么不可以这样呢:void UnsToStr(unsigned u,char *

15、str)char *pch;/* u超出范围吗?使用UlongToStr */ASSERT(u0);strcpy(str,pch);某些程序员对上面的代码感到很满意,因为它更有效并且更容易理解。它之所以更有效是因为strcpy比ReverseStr更快,特别是对于那些可把“调用”生成为内联指令的编译程序来说就更是这样。代码之所以更容易理解是因为C程序员对strcpy要更熟悉一些。当程序员见到ReverseStr时,就好象听到他们的朋友住进医院的消息一样,都会迟疑一下。这又说明什么呢?如果UnsToStr真是那么完美,我说这些干嘛!当然,它并不完美,事实上UnsToStr有个严重的缺陷。告诉我,str所指的存储空间多大?你并不知道。对于C语言接口程序来说,这并不罕见。在调用者和实现者之间有个无言的原则,这就是str将指向足够大

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 建筑/环境 > 施工组织

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号