fNewMemory、fResizeMemory和FreeMemory的内部有相应测试代码的情况下,他不是照样可以随意地在程序中使用这些函数吗?资料个人收集整理,勿做商业用途 那么如果他没有意识到fResizeMemory会引起内存块的移动,并因此在其程序中产生了类似于前述汇编程序中出现的错误,那么会发生什么现象呢?他需要因为执行了相应的一致性检查程序并产生了断言“illegal pointer”而对一致性检查程序的内容有所了解吗?资料个人收集整理,勿做商业用途 如果他创建并随即丢失了一个内存块,又会怎样呢?这时同样会执行相应的一致性检查程序并产生断言“lost memory”。也许,他甚至连什么叫做“lost memory”都不知道。但事实是,他并不需要知道这个,相应的检查就可以起作用。更妙的是,通过跟踪这一错误不用向有经验的程序员请教也可以学到与内存丢失有关的内容。资料个人收集整理,勿做商业用途 这就是精心设计子系统测试代码的好处 ─── 当测试代码将错误限制在一个局部的范围之内后,就通过断言把错误抓住并送到“广播室”,把正常的工作打断。对于程序员来说,这真是再好不过的反馈。资料个人收集整理,勿做商业用途
努力做到透明的一致性检查 我们交付的不是调试版本
在这一章中,我确实给内存管理程序加上了许多调试代码。对此,一些程序员可能会认为:“在程序中加入调试代码似乎很有用,但象这样把所有的检查都加上并且还包括了对日志信息的处理,就太过分了。”我得承认,我也有过这种感觉。资料个人收集整理,勿做商业用途 以前我也对给程序加上这么多降低效率的调试代码很反感,但不久我就认识到了自己的错误。在程序的交付版本中加上这种调试代码是会断送它的市场前途,但我们并没有在其交付版本中增加任何的测试代码,这些代码只是被用在了它的调试版本中。确实,调试代码会降低调试版本的运行速度。但使你的零售产品瘫在用户那儿,或者为了帮助查错使你的调试版本运行得稍慢,哪一种情况更糟糕呢?我们不应该担心调试版本的效率,因为毕竟顾客不会使用程序的调试版本。资料个人收集整理,勿做商业用途 重要的是要在感情上区分程序的调试版本和交付版本。调试版本事用来发现错误的,而交付版本则是用来取悦顾客的。因此在编码时,对这两个版本所作的权衡也会相当不同。资料个人收集整理,勿做商业用途 记住,只要相应的交付版本能够满足顾客的大小和速度要求,就可以对调试版本做你想做的任何事情。如果为内存管理程序加上日志程序可以帮助你发现各种难于捕捉的错误,那么就会皆大欢喜。顾客可以得到一个充满活力的程序而你不费很多的时间和精力就可以发现错误。资料个人收集整理,勿做商业用途 Microsoft的程序员总是在其程序中加上相应的调试代码。例如,Excel就含有一些内存子系统的测试程序(它们比我们这里介绍的还要详尽)。它有单元表格一致性检查程序;它有人为产生内存失败的机制,使程序员可以强制程序执行“内存空间耗尽”的错误处理程
21 / 24
序;它还有许多的其它检查程序。这不是说Excel的交付版本从来没有错误,它确实有,但这些错误很少出现在通过了详尽的子系统检查的代码中。资料个人收集整理,勿做商业用途 同样,虽然我们在这一章中给内存管理程序增加了许多的代码,但增加的所有代码都是用来构造fNewMemory、FreeMemory和fResizeMemory,我们没给这些函数的调用程序增加任何东西,也没给malloc、free和realloc的内部支持代码(它们可以非常重要)增加任何东西。甚至增加调试代码所引起的速度下降,也并非如想象的那样糟糕。如果Microsoft公司的统计结果具有代表性的话,程序调试版本(充满了断言和子系统测试)的速度大约应该是相应交付版本的一半。资料个人收集整理,勿做商业用途
不要把对交付版本的约束应用到相应的调试版本上 要用大小和速度来换取错误检查能力 确有其事
为了发现更多的错误,过去Microsoft总是把其开发的应用程序的调试版本送给β测试者进行β测试。但当基于产品的β调试版本对产品进行评论的“Pre-release”周刊出现,并且说其程序虽然非常好,但就是慢得和鼻涕虫一样之后,他们不再至少是暂时不再提供产品的β调试版本。这个事实告诫我们不要把调试版本送到测试场所,或者在这样做之前要把调试版本中影响性能的内部调试检查代码全部清除。资料个人收集整理,勿做商业用途
小结
在这一章中,我们介绍了六种增强内存子系统的方法。这些方法虽然是针对内存子系统提出来的,但其中的观点同样适用于其它的子系统。大家可以想象得出,在程序自己具有详尽的确认能力之后错误要想悄悄地溜入这种程序,简直比登天还难。同样,假如在我前面讲过的汇编程序中用上了这些调试检查,那么通常要花上几年才能发现的realloc错误,在相应代码第一次编写的几个小时或者几天之内就可以被自动地发现。不管程序员的技术很高,还是没有经验,这些测试代码都能够抓住这个错误。资料个人收集整理,勿做商业用途 事实上,这些测试代码能够抓住所有的这类错误。而且是自动地抓住,不靠运气,也不靠技巧
这就是编写无错代码的方法。
要点:
22 / 24
? 考查所编写的子系统,问自己:“在什么样的情况下,程序员在使用这些子系统时会犯错误。”在子系统中加上相应的断言和确认检查代码,以捕捉难于发现的错误和常见的错误。资料个人收集整理,勿做商业用途 ? 如果不能使错误不断重现,就无法排除它们。找出程序中可能引起随机行为的因素,并将它们从程序的调试版本中清除。把目前尚“无定义”的内存单元置成了某个常量值,就可能产生这种错误。在这种情况下,如果程序在该单元被正确地定义为某个值之前引用了它的内容,那么每次执行这部分错误的代码,都会得到同样的错误结果。资料个人收集整理,勿做商业用途 ? 如果所编写的子系统释放内存(或者其它的资源),并因此产生了“无用信息”,那么要把它搅乱,使它真的象无用信息。否则,这些被释放了的数据就有可能仍被使用,而又不会被注意到。资料个人收集整理,勿做商业用途 ? 类似地,如果在所编写的子系统中某些事情可能发生,那么要为该子系统加上相应的调试代码,使这些事情一定发生。这样可以增大查出通常得不到执行的代码中的错误的可能性。资料个人收集整理,勿做商业用途 ? 尽力使所编写的测试代码甚至在程序员对其没有感觉的情况下亦能起作用。最好的测试代码是不用知道其存在也能起作用的测试代码。资料个人收集整理,勿做商业用途 如果可能的话,把测试代码放到所编写的子系统中,而不要把它放到所编写子系统的外层。不要等到进行了系统编码时,才考虑其确认方法。在子系统设计的每一步,都要考虑“如何对这一实现进行详尽的确认”这一问题。如果发现这一设计难于测试或者不可能对其进行测试,那么要认真地考虑另一种不同的设计,即使这意味着用大小或速度作代价去换取该系统的测试能力也要这么做。资料个人收集整理,勿做商业用途 ? ? 在由于速度太慢或者占用的内存太多而抛弃一个确认测试程序之前,要三思而后行。切记,这些代码并不是存在于程序的交付版本中。如果发现自己正在想:“这个测试程序太慢、太大了”,那么要马上停下来问自己:“怎样才能保留这个测试程序,并使它既快又小?”资料个人收集整理,勿做商业用途 练习
1) 如果在进行代码测试时偶然碰到了0xA3的某种组合构成的数据,那么这一数据可能是未经过初始化的数据,或者是已被释放了的数据。怎样才能修改相应的凋试代码,使我们可以比较容易地确定所发现的数据是哪一类?资料个人收集整理,勿做商业用途 2) 程序员编写的代码有时会对所分配内存块上界之外的内存单元进行填充。请给出增加相应的内存子系统检查,使其能够对这类错误报警的方法。资料个人收集整理,勿做商业用途 3) 虽然CheckMemoryIntegrity程序被用来对悬挂指针错误进行检查,但在有些情况下,该程序检查不出这种错误。例如,假定一个函数调用了FreeMemory,但由于该函数的错误,某个指针被悬挂起来,即该指针指向的内存块已被FreeMemory释
23 / 24
放掉。现在我们进一步假定在该指针被确认之前,某个函数调用fNewMemory对这块刚释放不久的内存块进行了再分配。这样一来,刚才被悬挂起来的指针又指向了新分配的内存块。但是,这个内存块已经不是原来那个内存块了。因此,这是个错误。但对于CheckMemoryIntegrity来说却一切都很正常,并没有什么不合法。假如这个错误在你的项目中比较常见,那么怎样增强该程序才能使其查出这个问题呢?资料个人收集整理,勿做商业用途 4) 利用NoteMemoryRef程序,我们可以对程序中的所有指针进行确认。但是,我们如何对所分配内存块的大小进行确认呢?例如,假定指针指向的是一个含有18个字符的字符串,但所分配内存块的长度却小于18。或者在相反的情况下,程序认为所分配的内存块有15个字节,但相应的日志信息表明为其分配了18个字节。这两种情况都是错误的。怎样加强相应的一致性检查程序,使其能够查出这种问题?资料个人收集整理,勿做商业用途 5) NoteMemoryRef可以使我们把一个内存块标为“已被引用”,但利用它我们无法知道引用该内存块的指针数目是否超过了其应有的数目。例如,双向链表的每个结点只应该有两个引用。一个是前向指针,另一个是后向指针。但在大多数的情况下,每个内存块只应该有一个指针在引用着它。如果有多个指针同时引用一个内存块,那么一定是程序中什么地方出了错误。如何改进相应的一致性检查程序,使其对某些内存块允许多个指针对其进行同时的引用;但对另外一些内存块仍不允许多个指针对其进行同时的引用,并在这种情况发生时,引发相应的断言?资料个人收集整理,勿做商业用途 6) 本章自始自终所谈的都是为了帮助程序员检查错误,可以在相应的内存系统中加上调试代码。但是,我们可不可以增加对测试者有所帮助的代码呢?测试者知道程序经常会对错误情况进行不正确的处理,那么如何为测试者提供模拟“内存空间耗尽”这一条件的能力呢?资料个人收集整理,勿做商业用途
课题:
考查你项目中的主要子系统,看看为了检查出与使用这些子系统有关的常见错误,可以实现哪种类型的调试检查?
课题:
如果没有所用操作系统的调试版本,那么尽可能买一个。如果买不到,就利用外壳函数自己写一个。如果你助人为乐,那么请使所编出的代码(以某种方式)可以为其它的开发者所用。资料个人收集整理,勿做商业用途 24 / 24
优质C程序秘诀 - 章- 为子系统设防
![](/skin/haowen/images/icon_star.png)
![](/skin/haowen/images/icon_star.png)
![](/skin/haowen/images/icon_star.png)
![](/skin/haowen/images/icon_star.png)