为了更好地对程序调试,可以使用如下方法:使用断言、使用跟踪语句、使用异常和返回值。
一、断言
1、基本概念
断言是一种让错误在运行时候自我暴露的简单有效实用的技术。它们帮助你较早较轻易地发现错误,使得整个调试过程效率更高。
断言是布尔调试语句,用来检测在程序正常运行的时候某一个条件的值是否总为真,它能让错误在运行时刻暴露在程序员面前。使用断言的最大好处在于,能在更解决错误的发源地的地方发现错误。断言具有以下特征:
.断言是用来发现运行时刻错误的,发现的错误是关于程序实现方面的。
.断言中的布尔表达式显示的是某个对象或者状态的有效性而不是正确性。
.断言在条件编译后只存在于调试版本中,而不是发布版本里。
.断言不能包含程序代码。
.断言是为了给程序员而不是用户提供信息。
使用断言最根本的好处是自动发现许多运行时产生的错误,但断言不能发现所有错误。断言检查的是程序的有效性而不是正确性,可通过断言把错误限制在一个有限的范围内。当断言为假,激活调试器显示出错代码时,可用Call Stack命令,通过检查栈里的调用上下文、少量相关参数的值以及输出窗口中Debug表的内容,通常能检查出导致断言失败的原因。_ASSERTE宏(属于C运行时间库)还能在断言失败时显示出失效断言。下面我们讨论一下MFC库中的断言。
2、MFC库中的断言
(1) ASSERT(布尔表达式)
用MFC时最好选择ASSERT宏,它的优点是即使出现了WM_QUIT消息也能显示断言失效消息框。
(2) VERIFY(布尔表达式)
VERIFY宏中的布尔表达式在发布版本中被保留下来。VERIFY宏简化了对函数返回值的检查,一般用来检查Windows API的返回值。由于VERIFY宏里的布尔表达式在发布版本里保留了下来,因此最好尽量不要使用这个宏以实现程序代码和调试代码的完全分离。
(3 )ASSERT_VALID(指向CObject派生类对象的指针)
ASSERT_VALID宏通过调用重载的AssertValid函数来确定指向CObject派生类对象的指针是否有效。无论你什么时候从CObject派生类中得到一个对象,在对这个对象做任何操作之前都应该调用ASSERT_VALID宏。
(4) ASSERT_KINDOF(类名, 指向CObject派生类对象的指针)
这个宏用来验证指向CObject派生类对象的指针是否从某个特殊类中派生,在调用它之前先调用ASSERT_VALID宏。只有在很特殊的场合下才用得到,如检测编译器可能错过的对象类型问题。
此外,还有两个没有正式文件的ASSERT宏的变种:ASSERT_POINTER(指针,指针类型),ASSERT_NULL_OR_POINTER(指针,指针类型)。
3、什么时候使用断言
把断言看作一种简单的制造栅栏的方法,这种栅栏能使错误在穿过自己时暴露。
.检查函数的输入
.检查函数的输出
.检查对象的当前状态
.坚持逻辑变量的合理性和一致性
.检查类中的不变量
公有成员函数比私有和保护的成员函数需要更全面的断言。
不正确地使用断言会导致错误。断言应该检测那些在程序正常运行的时候永远都不可能出现的状态。断言是用来揭示错误的,而不是用来纠正运行时刻错误的。
4、断言与防御性编程(Defensive Programming)
断言在调试的时候向程序员揭示运行时刻错误(调试版本里),而防御性编程使用户在运行程序(发布版本里)时,当出现意外情况时程序仍能继续工作。实际上,防御性的编程要求程序在检测到意外时返回一个“安全”的值(比如布尔函数返回false,指针和句柄返回空值),一个错误代码或者抛出一个异常来解决问题。特定的防御性编程技术包括:处理无效函数参数和数据、出现问题的时候程序失败、检查临界函数返回的错误代码以及处理异常。需要防御性编程的标准问题包括:错误的输入数据、内存或者硬盘空间不够、不能打开一个文件、外部设备不能访问、网络连接不上或者甚至在程序中还有错误,目的是保持程序的运行状态。如果你的程序是防御性的,别忘了使用断言。如果你使用断言,也别忘了防御性编程。这两种技术最好结合在一起使用。
一、断言
1、基本概念
断言是一种让错误在运行时候自我暴露的简单有效实用的技术。它们帮助你较早较轻易地发现错误,使得整个调试过程效率更高。
断言是布尔调试语句,用来检测在程序正常运行的时候某一个条件的值是否总为真,它能让错误在运行时刻暴露在程序员面前。使用断言的最大好处在于,能在更解决错误的发源地的地方发现错误。断言具有以下特征:
.断言是用来发现运行时刻错误的,发现的错误是关于程序实现方面的。
.断言中的布尔表达式显示的是某个对象或者状态的有效性而不是正确性。
.断言在条件编译后只存在于调试版本中,而不是发布版本里。
.断言不能包含程序代码。
.断言是为了给程序员而不是用户提供信息。
使用断言最根本的好处是自动发现许多运行时产生的错误,但断言不能发现所有错误。断言检查的是程序的有效性而不是正确性,可通过断言把错误限制在一个有限的范围内。当断言为假,激活调试器显示出错代码时,可用Call Stack命令,通过检查栈里的调用上下文、少量相关参数的值以及输出窗口中Debug表的内容,通常能检查出导致断言失败的原因。_ASSERTE宏(属于C运行时间库)还能在断言失败时显示出失效断言。下面我们讨论一下MFC库中的断言。
2、MFC库中的断言
(1) ASSERT(布尔表达式)
用MFC时最好选择ASSERT宏,它的优点是即使出现了WM_QUIT消息也能显示断言失效消息框。
(2) VERIFY(布尔表达式)
VERIFY宏中的布尔表达式在发布版本中被保留下来。VERIFY宏简化了对函数返回值的检查,一般用来检查Windows API的返回值。由于VERIFY宏里的布尔表达式在发布版本里保留了下来,因此最好尽量不要使用这个宏以实现程序代码和调试代码的完全分离。
(3 )ASSERT_VALID(指向CObject派生类对象的指针)
ASSERT_VALID宏通过调用重载的AssertValid函数来确定指向CObject派生类对象的指针是否有效。无论你什么时候从CObject派生类中得到一个对象,在对这个对象做任何操作之前都应该调用ASSERT_VALID宏。
(4) ASSERT_KINDOF(类名, 指向CObject派生类对象的指针)
这个宏用来验证指向CObject派生类对象的指针是否从某个特殊类中派生,在调用它之前先调用ASSERT_VALID宏。只有在很特殊的场合下才用得到,如检测编译器可能错过的对象类型问题。
此外,还有两个没有正式文件的ASSERT宏的变种:ASSERT_POINTER(指针,指针类型),ASSERT_NULL_OR_POINTER(指针,指针类型)。
3、什么时候使用断言
把断言看作一种简单的制造栅栏的方法,这种栅栏能使错误在穿过自己时暴露。
.检查函数的输入
.检查函数的输出
.检查对象的当前状态
.坚持逻辑变量的合理性和一致性
.检查类中的不变量
公有成员函数比私有和保护的成员函数需要更全面的断言。
不正确地使用断言会导致错误。断言应该检测那些在程序正常运行的时候永远都不可能出现的状态。断言是用来揭示错误的,而不是用来纠正运行时刻错误的。
4、断言与防御性编程(Defensive Programming)
断言在调试的时候向程序员揭示运行时刻错误(调试版本里),而防御性编程使用户在运行程序(发布版本里)时,当出现意外情况时程序仍能继续工作。实际上,防御性的编程要求程序在检测到意外时返回一个“安全”的值(比如布尔函数返回false,指针和句柄返回空值),一个错误代码或者抛出一个异常来解决问题。特定的防御性编程技术包括:处理无效函数参数和数据、出现问题的时候程序失败、检查临界函数返回的错误代码以及处理异常。需要防御性编程的标准问题包括:错误的输入数据、内存或者硬盘空间不够、不能打开一个文件、外部设备不能访问、网络连接不上或者甚至在程序中还有错误,目的是保持程序的运行状态。如果你的程序是防御性的,别忘了使用断言。如果你使用断言,也别忘了防御性编程。这两种技术最好结合在一起使用。