T-提示 Z-总结 Lx-分类 CODE-代码
/////////////参考《Debugging Windows Programs》-DWP 《游戏编码精粹》
////////////
编译器-通过静态分析代码来查错.
断言-程序自己自动检测运行时刻错误.
提示
T-断言是能让错误在运行时刻暴露在程序员面前的布尔调试语句.
T-使用断言能使问题早日发现,尽量在促无发源地附近就能够检测道错误.
T-使用断言来验证你的程序是否有效,但是记住,有效和正确并不相等. //日期的有效和正确
T-断言不能代替细致的测试.
T-保持断言的简单性.好的覆盖比复杂的检测更重要.
类型
L1-ANSI C断言.
assert函数 需包含 <assert.h> 链接C运行时刻函数库 定义NDEBUG符号驱动
显示失效表达式 路径名长度有限
L2-C运行时刻函数库断言
需包含 <crtdbg.h> 链接C运行时刻函数库 是VC++中的特殊断言/不是VC++自带
_ASSERT 不显示失效表达式
_ASSERTE 显示失效表达式 不支持Unicode
CODE-////from crtdbg.h
#define _ASSERT(expr) do { if (!(expr) && (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, NULL))) _CrtDbgBreak(); } while (0)
#define _ASSERTE(expr) do { if (!(expr) && (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, #expr))) _CrtDbgBreak(); } while (0)
T-_ASSERTE宏显示了失效断言,更加方便,并能提供一种保护机制,使你不会意外调试了不被调试的问题.
Z-在C运行时刻函数库中的断言里,_ASSERTE宏是最好的选择.
L3-MFC库的断言
ASSERT 宏和 _ASSERT 宏显示的消息框相同. 即使出现了WM_QUIT消息也能显示断言失效消息框.
CODE-
////from Afx.h
#define ASSERT(f) do { if (!(f) && AfxAssertFailedLine(THIS_FILE, __LINE__)) AfxDebugBreak(); } while (0) ////from Afxver.h
#define AfxDebugBreak() _CrtDbgBreak()
////from Afxassert.cpp
BOOL AFXAPI AfxAssertFailedLine(LPCSTR lpszFileName, int nLine);
mfc还提供了 ASSERT 宏的几个变种.
更多MFC断言宏
L31-VERIFY
使用之的唯一原因: 它的保留程序代码的能力.
妨碍防御性编程(defensive programming)
L32-ASSERT_VALID
决定一个指向 CObject 派生类的对象的指针是否有效.
通过调用重载的 AssertValid 函数来决定 CObject 派生类的对象是否有效.
还进行了其它几个检查,增强了健壮性.
CODE- from Afx.h ////DWP-P73
#define ASSERT_VALID(pOb) (::AfxAssertValidObject(pOb, THIS_FILE, __LINE__))
T-在使用 CObject 派生类的对象之前都要调用 ASSERT_VALID 宏
L33-ASSERT_KINDOF
验证指向 CObject 派生类的对象的指针是否从特殊类中派生来.
CODE-ASSERT_KINDOF/////
#define ASSERT_KINDOF(class_name, object) ASSERT((object)->IsKindOf(RUNTIME_CLASS(class_name)))
T-总是在调用 ASSERT_KINDOF 宏之前调用 ASSERT_VALID 宏
eg-
CMyDocument * CMyView::GetDocument(){
ASSERT_VALID(m_pDoc);
ASSERT_KINDOF(CMyDocument, m_pDoc); //ASSERT(m_pDoc->IsKindOf(RUNTIME_CLASS(CMyDocument)));
......}
T-使用 ASSERT_KINDOF 宏检测编译器可能错过的对象类型问题.
L34-ASSERT_POINTER ASSERT_NULL_OR_POINTER
当指针不能为空的时候使用 ASSERT_POINTER; 否则使用 ASSERT_NULL_OR_POINTER
CODE-////from Afx.h
#define ASSERT_POINTER(p, type) ASSERT(((p) != NULL) && AfxIsValidAddress((p), sizeof(type), FALSE))
#define ASSERT_NULL_OR_POINTER(p, type) ASSERT(((p) == NULL) || AfxIsValidAddress((p), sizeof(type), FALSE))
BOOL AFXAPI AfxIsValidAddress(const void* lp,
UINT nBytes, BOOL bReadWrite = TRUE);
BOOL AFXAPI AfxIsValidString(LPCWSTR lpsz, int nLength = -1);
BOOL AFXAPI AfxIsValidString(LPCSTR lpsz, int nLength = -1);
以上两个函数都是对 IsBadReadPtr、IsBadWritePtr和IsBadStringPtr API 的简单封装.
L4-ATL断言
<Atlbase.h> 包含 <crtdbg.h>
ATLASSERT 默认实现为 _ASSERTE 可方便地使用自定义的断言.
T-在ATL程序中使用ATLASSERT;用MFC时选择ASSERT宏.
CODE-断言的源代码
/////DWP-P69
T-移植代码的时候,也移植断言
策略
T-要想有效利用断言需要一定的策略,不要随意地把断言分布道你的代码里面,而是遵从某些已经建立起来的模式.
T-"对整个时间都下断言",也就是说,不要试图选择哪一个断言要发现错误,根据你的断言策略断言一切.
L1-什么需要断言
L2-什么不需要断言
不变关系
L1-类不变关系
T-设计程序的同时就设计不变关系.在写代码之前理解它们,并且为它们建立文档.
T-公有成员函数比私有和保护的成员函数需要更全面的断言.
在函数进/出口使用断言,也不能保证中间变的临时无效.
模式
T-使用最能匹配你的数据的断言.
L1-函数参数断言
检查函数参数的有效性,也可以对作为隐式参数的全局变量采用同样的检查.
eg-////DWP-P86
_ASSERTE(value <= maxValue); ...
L2-CObject函数断言
对公有函数 ASSERT_VALID(this);
对私有函数 对其使用到的成员变量 断言
对复杂的函数 在其开头,结尾都使用断言 ASSERT_VALID(this);
对操作符函数 确保不是对自己操作 _ASSERTE(pObject != this)
AssertValid() 1.baseclass::AssertValid(); 2.对自己的成员变量断言 3.类不变关系断言
L3-派生数据断言
eg-////DWP-P89
L4-逻辑断言
1.switch-case 2.while(someloop) 3.经过复杂运算后,断言运算结果.(用不同的算法)
4.内存断言 _ASSERTE(_CrtCheckMemory());
L5-Windows API断言
处理消息时,在消息出错时断言. 对从 wParam, lParam 派生的参数使用断言.
eg-
1._ASSERTE(IsWindow(hWnd));
2.HWND hChildwnd = (HWND)wParam; _ASSERTE(IsWindow(hChildwnd));
3.HDC hDC = (HDC)wParam; _ASSERTE(GetObjectType(hDC) == OBJ_DC);
4.BYTE *buffer = (BYTE *)lParam; ASSERT_POINTER(buffer, BYTE);
T-总要为那些不清洗的断言做出注释,给别人和自己一个目前情况的线索.
T-充分实现 AssertValid 函数, 不要使用默认实现.
T-通过 AssertValid 函数检查对象的类不变关系.对简单的数据成员使用功能强大的断言;而对集合数据成员采用浅层检查.(shallow check)
避免出现断言"瀑布" (cascade)
防御性编程 (Defensive Programming)
T-不正确地使用断言会导致错误.断言是用来接示错误的,而不示用来纠正运行时刻错误的.
T-垃圾输入不应该导致垃圾输出.
T-Debug 版本和 Release 版本应该一样.//只用断言,没防御性编程很可能会导致不一致.
T-如果你的程序是防御性的,别忘了使用断言;如果你使用了断言,也别忘了防御性编程.
eg- if (!complexExpression){ _ASSERTE(FALSE);...}
if (FALIED(someFunction()){ _ASSERTE(!"the problem is XXX");
T-不要把不相关的检查放在同一个断言语句中.
使用技巧
L1- assert(scr != 0 && "FunName: scr point is NULL.");
L2- assert(!"FunName: the reason."); // == assert(0);
L3- #define Assert(a, b) assert(a && b)
Assert(scr != 0, "FunName: scr point is NULL.");
L4-重定向输出
int previousReportMode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
ASSERT_VALID(pDoc);
//....
if (previousReportMode != -1) _CrtSetReportMode(_CRT_ASSERT, previousReportMode);
L5-使用 GetObjectType 函数
_ASSERTE(GetObjectType(hBrush) == OBJ_BRUSH); //