首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

自各儿设计并使用断言

2012-09-20 
自己设计并使用断言断言读者还要意识到,一旦程序员学会了使用断言,就常常会对宏assert进行重定义。例如,程

自己设计并使用断言

断言

读者还要意识到,一旦程序员学会了使用断言,就常常会对宏assert进行重定义。例如,程序员可以把assert定义成当发生错误时不是中止调用程序的执行,而是在发生错误的位置转入调试程序。assert的某些版本甚至还可以允许用户选择让程序继续运行,就仿佛从来没有发生过错误一样。

如果用户要定义自己的断言宏,为不影响标准assert的使用,最好使用其它的名字。本书将使用一个与标准不同的断言宏,因为它是非标准的,所以我给它起名叫做ASSERT,以使它在程序中显得比较突出。宏assert和ASSERT之间的主要区别是assert是个在程序中可以随便使用的表达式,而ASSERT则是一个比较受限制的语句。例如使用assert,你可以写成:

if(assert(p != NULL), p->foo!=bar)

……

但如果用ASSERT试试就会产生语法错误。这种区别是作者有意造成的。除非打算在表达式环境中使用断言,否则就应该将ASSERT定义为语句。只有这样,编译程序才能够在它被错误地用到表达式时产生语法错误。记住,在同错误进行斗争时每一点帮助都有助于错误的发现。我们为什么要那些自己从来用不着的灵活性呢?

下面是一种用户自己定义宏ASSERT的方法:

#ifdef DEBUG

    void _Assert(char* , unsigned);     /* 原型 */

#define ASSERT(f)           \

    if(f)                       \

        NULL;               \

    else                        \

        _Assert(__FILE__ , __LINE__)

#else

    #define ASSERT(f)           NULL

#endif

从中我们可以看到,如果定义了DEBUG,ASSERT将被扩展为一个if语句。if语句中的NULL语句让人感到很奇怪,这是因为要避免if不配对,所以它必须要有else语句。也许读者认为在_Assert调用的闭括号之后需要一个分号,但并不需要。因为用户在使用ASSERT时,已经给出了一个分号.

当ASSERT失败时,它就使用预处理程序根据宏__FILE__和__LINE__所提供的文件名和行号参数调用_Assert。_Assert在标准错误输出设备stderr上打印一条错误消息,然后中止:

void _Assert(char* strFile, unsigned uLine)

{

    fflush(stdout);

    fprintf(stderr, “\nAssertion failed: %s, line %u\n”,strFile, uLine);

    fflush(stderr);

    abort();

}

在执行abort之前,需要调用fflush将所有的缓冲输出写到标准输出设备stdout上。同样,如果stdout和stderr都指向同一个设备,fflush stdout仍然要放在fflush stderr之前,以确保只有在所有的输出都送到stdout之后,fprintf才显示相应的错误信息。

现在如果用NULL指针调用memcpy,ASSERT就会抓住这个错误,并显示出如下的错误信息:

Assertion failed: string.c , line 153

这给出了assert与ASSERT之间的另一点不同。标准宏assert除了给出以上信息之外,还显示出已经失败了的测试条件。例如对这个问题,我通常所用编译程序的assert会显示出如下信息:

Assertion failed: pvTo != NULL && pbFrom != NULL

File string.c , line 153

在错误消息中包括测试表达式的唯一麻烦是每当使用assert时,它都必须为_Assert产生一条与该条件对应的正文形式打印消息。但问题是,编译程序要在哪儿存储这个字符串呢?Macintosh、DOS和Windows上的编译程序通常在全局数据区存储字符串,但在Macintosh上,通常把最大的全局数据区限制为32K,在DOS和Windows上限制为64K。因此对于象Microsoft Word和Excel这样的大程序,断言字符串立刻会占掉这块内存。

关于这个问题存在一些解决的办法,但最容易的办法是在错误信息中省去测试表达式字符串。毕竟只要查看了string.c的第153行,就会知道出了什么问题以及相应的测试条件是什么。

 

要点

l  要同时维护交付和调试两个版本。封装交付的版本,应尽可能地使用调试版本进行自动查错。

l  断言是进行调试检查的简单方法。要使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是在最终产品中必须处理的。

l  使用断言对函数的参数进行确认,并且在程序员使用了无定义的特性时向程序员报警。函数定义得越严格,确认其参数就越容易。

l  在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了相应的假定,就要使用断言对所做的假定进行检验,或者重新编写代码去掉相应的假定。另外,还要问:“这个程序中最可能出错的是什么,怎样才能自动地查出相应的错误?”努力编写出能够尽早查出错误的测试程序。

l  一般教科书都鼓励程序员进行防错性程序设计,但要记住这种编码风格会隐瞒错误。当进行防错性编码时如果“不可能发生”的情况确实发生了,要使用断言进行报警。

 

源码事例(几个遇到错误但忽略的断言源码):

#define _ASSERTE_RT(expr) \
        _ASSERTE(expr);  \
      if(!(expr))  return; \

#define _ASSERTE_RT_BL(expr) \
        _ASSERTE(expr);  \
      if(!(expr))  return FALSE; \

#define _ASSERTE_RT_UI(expr) \
 _ASSERTE(expr);  \
 if(!(expr))  return 0; \

#define _ASSERTE_RT_DBL(expr) \
 _ASSERTE(expr);  \
 if(!(expr))  return 0.; \

 

 

热点排行