windows程序调试工具包 集,Windows程序调试
第一章是调试过程
1.成功和高效调试的关键是找到准确的错误信息。
2.一旦发现一个错误,就有可能发现更多。相似的代码可能有相似的错误。
3.从错误中学习如何防止未来的错误。
4.对于新代码,不需要执行测试来确定它是否有错误。
第二章:写调试用的C代码。
c语言和编程风格
1.需要时使用语言的高级功能。
2.要写出“人”能看懂的代码,不仅仅是编译器
3.谨慎使用匈牙利命名法
4.每个语句行都应该作为一个独立的原子单元,这样可以充分利用调试工具。
5.如果你不确定是否需要括号,那么你就需要括号。
6.利用C语言自身特点防止错误的方法:
请使用const而不是#define来创建常数;
请使用enum而不是#define来创建常数集;
使用内联函数而不是# define macro
替换malloc和free用新建和删除;
I/O流而不是标准I/O.
7.在头文件中重新声明所有共享的外部符号,并将参数名保留在函数原型中,以便于理解。
8.总是创建发布版本来帮助检查未初始化的变量,因为优化器会检查变量的使用。
9.使用Limits.h中的最大/最小值作为整数数据类型的大小限制,浮点类型的最大/最小值在Float.h中定义
10.在VC中,字符类型默认是带符号的[-128 ~ 127]
11.不到万不得已不要用cast。
Dynamic_cast:用于多态类型的转换(编译器的RTTI必须打开)
Static_cast:用于非多态类型的转换
Const_cast:用于移除Const、volatile和__unaligned(x64)属性。
Reinterpret_cast:用于转换不兼容的数据类型,比如指针和非指针。
12.在程序中小心使用const可以在编译过程中发现更多的错误。
13.程序完成后不要添加const。一开始就正确严格地使用const。
14.如果每次循环时都需要增加/减少循环变量,请使用for而不是while。
15.处理可能出错的构造函数的方法:
先在构造函数中执行基本的初始化不出错,在另一个函数(init)中执行可能出错的初始化操作。
第二,使用异常机制在初始化过程中捕获异常,然后释放初始化的资源(也可以使用auto_ptr智能指针)并抛出异常。
16.如果在处理异常时调用析构函数,并且析构函数产生未处理的异常,程序将被终止。因为堆栈扩展时抛出的异常会终止整个程序。
17.由于第16条中描述的原因,析构函数中的异常必须在析构函数中处理。
18.如果一个类需要一个析构函数来释放由构造函数分配的资源,那么要么提供复制构造函数和赋值操作符,要么使用private来避免它们被自动添加。
19.“三大法则”:如果一个类需要一个析构函数,或者一个复制构造函数,或者一个复制操作符,那么它需要这三个。
可视化编译器
1.始终使用/W4警告级别。
2.在调试版本中使用/GZ选项有助于发现只有在发布版本中才能发现的错误。
3.使用#pragma warning来控制和调整特定的警告(有关详细信息,请参见VC文档)。使用# pragma时最好给出明确的注释。
4.在消除编译警告时,仔细检查原因,不是直接原因,而是隐藏的警告根本原因。最终目标是消除错误,而不是警告。
第三章使用断言
1.断言只能用来检查有效性,而不是正确性。
2.保持断言简单。良好的覆盖面
3.在C语言运行时库中使用断言时,用_ assert代替_ assert;使用MFC库时使用ASSERT宏
4.在ATL程序中使用ATLASSERT允许您使用自定义断言
5.verify一般用于检测Windows API的返回值(建议用ASSERT代替VERIFY宏)
6.在使用CObject类的派生类对象之前,调用ASSERT_VALID宏
7.公共函数比私有和受保护的函数需要更全面的断言。
8.对不太明显的断言做出明确的评论。
9.任何垃圾输入都不应该导致垃圾输出。
第四章使用跟踪报表
1.Windows API: OutputDebugString函数,可以在调试/发布版本中运行,适用于跟踪开始/结束。
2.VC: _RPTn和_RPTFn系列函数的c运行时
3.MFC中的TRACE(n):
AfxOutputDebugString宏:在调试版本中等于_RPT0,在发布版本中等于OutputDebugString。
CObject:Dump函数:
AfxDump全局对象:
AfxDumpStack函数:
4.AtlTrace语句:atlchase和atlchase 2函数
第5章使用异常和返回值
1.不要使用异常处理来掩盖不可恢复的错误。
2.异常是在每个线程的基础上引发和处理的,但未处理的异常将结束整个过程。
3.异常处理需要很多额外的操作,所以不适合经常运行的代码。
4.经常发生的事情很可能不是真正的错误。
5.下列情况适合使用返回值(而不是异常):
用于指示非错误状态信息。
用于大多数情况下可以随意忽略而不会导致问题的错误。
用于循环中更有可能发生的错误(异常需要更多的开销)
用于中间语言模块(如COM)中的错误
6.您必须使用/Eha调试器选项来捕获使用C异常机制的操作系统异常。
7.您可以使用_ set _ se _ translator函数安装一个转换器,将SE转换为线程的C异常。
8.只需捕捉浮点溢出、零除和访问冲突,其他se无法恢复。
9.使用SetUnhandledExceptionFilter函数来打开程序崩溃处理程序
10.在极少数情况下使用异常处理机制不会影响性能,但可能会提高性能。
11.抛出异常的时机:当一个函数发生错误,不采取一些特殊措施函数就无法继续运行,而这些措施又无法自己完成时,就要抛出异常。
12.捕捉异常的时机:
当函数知道如何处理这个异常时
当这个函数知道如何处理这个异常,而高级函数不知道时。
当抛出异常可能导致进程崩溃时
当分配的资源需要分类时
13.异常报告的标准类是在c标准库中的stdexcept中定义的。
14.对可能偶然发生的错误情况使用异常。
15.MFC的异常应该使用指针来捕捉和应用删除成员函数释放,因为它们通常是在堆中分配的;其他C异常应该通过引用捕获,不需要手动释放,因为通过引用捕获的异常会在堆栈中传递,并保持多态。
16.异常规范(不能与模板混合):
函数()——可以抛出任何异常
函数throw()——不能抛出异常。
function throw(ce exception)3354只能引发基于ce exception的异常。
注意:不建议使用异常规范,这可能会导致程序因意外异常而崩溃,VC也不支持。
17.您可以使用_controlfp函数让浮点数抛出异常。
调试工具
第6章Windows中的调试
1.可以在VC调试器的监控中输入“@ERR”显示最新的LastError值,也可以使用“@ERR,hr”显示错误代码对应的文本描述信息。还可以通过FormatMessage函数将错误代码转换为文本格式。
2.创建一个映射文件用于以后的调试,创建时添加/MAPINFO:EXPORTS编译选项输出导出序列号。
3.如果崩溃没有发生在您的代码中,您可以通过堆栈转储信息将地址返回到您的代码中。
4.使用命令行工具Undname可以解析映射文件中的混合函数名。
第7章用VC调试器调试
1.
2.在调试版本中,默认情况下内联是关闭的
3.为调试版本和发布版本创建调试符号,并将PDB文件存档。
4.调试版本和发布版本都必须经过测试,不能假定它们以相同的方式运行。
5.
调试技术
第八章基本调试技术
1.使用Break函数调试无限循环。
2.在监视窗口中使用@CLK,d @CLK=0来观察每一步的执行时间。
3.在EAX或EAX EDX寄存器中检查函数的返回值或返回数据的指针。
4.可以通过GdiSetBatchLimit函数设置或关闭GDI的批处理,从而调试绘图代码。
5.使用GetAsyncKeyState函数调试WM_MOUSEMOVE消息
6.与使用Spy调试消息相关的问题
7.通过调用FromHandle函数返回的任何对象都不能跨邮件保存。只是MFC临时创建的一个对象,随时可能被销毁。
第九章内存调试
1.内存泄漏通常是其他程序错误和不良编程习惯的标志。
2.应该总是要求new在失败时抛出异常,而不是返回一个容易被忽略的空指针。
3.
4.使用库函数可以有更多的控制能力和更好的可移植性,但是使用MFC函数会稍微方便一些。
5.通过定义_CRTDBG_MAP_ALLOC,将程序中的所有堆函数都映射到它们的调试版本上,这样就可以得到带有源文件和行号的调试信息。
第10章调试多线程程序
1.使用互锁的串行锁定函数实现单个整数变量的线程安全操作。
2.当锁竞争不大时,使用临界区的效率比锁高,但临界区只在同一个进程内使用,不允许设置超时参数,也不能同时申请多个临界区的所有权。
3.在WatiForSingleObject函数中使用适当的超时参数可以有效地避免死锁。
4.当使用互斥体(非临界区)时,使用WaitForMultipleObjects函数是避免死锁的最有效方法。
5.处理与时间有关的问题的最好方法是让潜在的错误发生。
6.使用volatile关键字可以防止多线程程序中由编译器优化导致的变量访问错误。
7.创建一个“自动锁”类,利用它的构造函数和析构函数来获取和释放锁资源,这样即使发生异常也能正确释放锁资源。
8.尽量使用最高级别的线程来创建和清理函数,避免资源泄漏和未处理的异常。比如用MFC框架开发时,用MFC提供的AfxBeginThread和AfxEndThread函数代替CRT的_beginthreadex和_endthreadex函数和WindowsAPI的CreateThread和ExitThread函数。
9.如果MFC需要在一个线程中使用,那么这个线程必须由MFC创建。
10.关闭MFC线程的正确方法之一(CWinThread对象会调用自己的析构函数):
纠正方法2(不要让CWinThread对象自毁):
1.OutputDebugString函数用于在并发执行的多个线程中输出调试信息,这可以序列化线程的执行。
12.在跟踪一段可以被多线程调用的代码时,不能假设你现在所处的上下文就是你刚才所处的环境。可以通过观察局部变量的变化或者调用堆栈来判断上下文是否已经切换,也可以查看线程对话框来确定当前线程。
13.阻塞函数的超时是以实际时间来衡量的,而不是有效的CPU时间,程序中断后的时间也会被记录为超时,所以正常运行时不太可能出现的超时错误往往是在调试时造成的。
14.在调试多线程代码之前,确定这个问题是否与版本、调试器、系统和处理器有关。从这些信息中,您可以知道在使用调试器时应该首先尝试什么。
15.出现错误时,使用“线程”对话框查看其他线程是否正在处理与错误线程相同的数据。
16.使用dw( @TIB0x24)检查当前线程的ThreadID,可以避免频繁的在线程对话框和代码窗口之间切换(在WinXP中不一定是这个值,待定)
第十一章通讯器调试
第十二章非常规策略