windows程序调试

windows程序调试,第1张

调试策略

第一章         调试的过程

1.         成功而高效的调试的关键是找到准确的错误信息

2.         一旦找到一个错误,就可能找到更多。类似的代码可能还有类似的错误

3.         从错误中学习如何预防将来会产生的错误

4.         对于新代码,根本不需要执行测试来判断它是否有错误

第二章         编写便于调试的C++代码

C++语言和编程风格

1.         在需要的时候使用语言的高级特性

2.         要写出能被“人”理解的代码,不仅是编译器

3.         慎用匈牙利命名法

4.         每一个语句行都应该作为一个单独的原子单位,这样可以充分利用调试工具

5.         如果你不能确定是否需要括号,那么就需要括号

6.         使用C++自身特性防止错误的方法:

用const代替#define来创建常量;

用enum代替#define来创建常量集合;

用inline函数代替#define宏;

用new和delete代替malloc和free;

用I/O Streams代替Standard I/O。

7.         在头文件重声明所有的共享外部符号,并且保留函数原形中的参数名以便于理解

8.         经常创建Release版本来帮助检查未初始化的变量,因为变量的使用是由优化器来检查的

9.         用Limits.h中的最大/小值作为整型数据类型的大小限制,浮点类型的最大/小值定义在Float.h中

10.     在VC++中默认情况下字符类型是有符号的[-128 ~ 127]

11.     不在迫不得已的情况下,不要使用强制类型转换

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.     由于No.16所述原因,所以析构函数中的异常一定要在析构函数中得到处理

18.     如果一个类需要析构函数来释放构造函数分配的资源,那么要么提供拷贝构造函数和赋值运算符,要么使用private避免它们被自动添加

19.     “大三法则”:如果一个类需要一个析构函数,或者一个拷贝构造函数,或者一个复制运算符,那么它就三个都需要

Visual C++编译器

1.         总是使用/W4警告级别

2.         在调试版中使用/GZ选项,可以帮助发现在Release版里才能发现的错误

3.         使用#pragma warnning来控制和调整特定的警告(具体参见VC++文档),使用#pragma时最好给出明确的注释

4.         在消除编译警告时要仔细检查原因,不是直接原因,而是隐藏的导致警告的根本原因。最终目标是消除错误,而不是消除警告

第三章         使用断言

1.         断言只能用于检查有效性,而不是正确性

2.         保持断言的简单性。好的覆盖

3.         在使用C语言运行时库中的断言时,使用_ASSERTE而不是_ASSERT;使用MFC库时使用ASSERT宏

4.         在ATL程序中使用ATLASSERT可以让你使用自定义的断言

5.         VERIFY一般用来检测Windows API的返回值(建议不使用VERIFY宏,而使用ASSERT)

6.         在使用CObject类的派生类对象前调用ASSERT_VALID宏

7.         公有函数比私有和保护函数需要更全面的断言

8.         给那些不是这么显而易见的断言做出清晰的注释

9.         任何垃圾输入都不应该导致垃圾输出

第四章         使用跟踪语句

1.         Windows API: OutputDebugString函数,可在调试/发布版本中运行,适合跟踪启动/结束

2.         VC++的C运行时: _RPTn和_RPTFn系列函数

3.         MFC中的TRACE(n):

AfxOutputDebugString宏: 调试版中等于_RPT0,发布版中等于OutputDebugString

CObject::Dump函数:

AfxDump全局对象:

AfxDumpStack函数:

4.         ATL跟踪语句: AtlTrace和AtlTrace2函数

第五章         使用异常和返回值

1.         不要使用异常处理来屏蔽不可重获得错误

2.         异常是基于每个线程而提出和处理的,但是未处理的异常会使整个进程结束

3.         异常处理需要大量的额外 *** 作,因此它不适合用于经常运行的代码

4.         经常发生的情况很可能并不是真正的错误

5.         以下几种情形适合使用返回值(而不是异常):

用于指示非错误的状态信息

用于大多数情况下可以随意忽略而不会出问题的错误

用于更易于出现在循环中的错误(异常需要更多额外开销)

用于中间语言模块中的错误,如COM

6.         必须使用/Eha调试器选项才能捕获使用C++异常机制的 *** 作系统异常

7.         可以使用­_set_se_translator函数来为一个线程安装一个把SE转换到C++异常的转换器

8.         只有浮点溢出、零除和访问冲突这三类SE应该捕捉,其它的SE是不可恢复的

9.         使用SetUnhandledExceptionFilter函数安转一个程序崩溃处理器

10.     在很少出现异常的情况下使用异常处理机制不会影响性能,反而有可能提升性能

11.     抛出异常的时机:当一个函数发生错误时,并且如果不采取一些特殊措施函数就不能继续运行,而这些措施它自己又不能完成的情况下,应该抛出异常

12.     捕获异常的时机:

当函数知道如何处理这个异常时

当这个函数知道如何处理这个异常,而高级函数不知道如何处理时

当抛出异常可能使进程崩溃时

当需要整理分配好的资源时

13.     在C++标准库中的<stdexcept>中定义了用于异常报告的标准类

14.     对只可能意外发生的错误情况使用异常

15.     MFC的异常应使用指针来捕获并适用Delete成员函数释放,因为它们通常在堆中分配;其它C++异常应使用引用来捕获,也不需要手动释放,因为引用捕获的异常会在栈中传递且保持了多态性

16.     异常规范(不能和模板混合使用):

Function()——可以抛出任何异常

Function throw()——不能抛出异常

Function throw(CException)——只能抛出以CException为基类的异常

注意:不推荐使用异常规范,它可能由于未预料的异常而导致程序崩溃,且VC不支持

17.     可以使用_controlfp函数来让浮点数抛出异常

调试工具

第六章         在Windows中调试

1.         可以在VC调试器的监视中输入“@ERR”来显示最新的LastError值,也可以使用“@ERR,hr”来显示错误代码对应的文本描述信息。还可以通过FormatMessage函数将错误代码转换到文本格式

2.         创建映射文件以便于事后调试,创建时添加/MAPINFO:EXPORTS编译选项用于输出导出序号

3.         如果崩溃不是发生在你的代码内部,可以通过堆栈转储信息得到返回到你的代码的地址

4.         使用Undname命令行工具可以用于解析映射文件中的混合函数名

第七章         使用VC++调试器调试

1.         

2.         在调试版中,内联默认是被关闭的

3.         为Debug版和Release版都创建调试符号,并将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来将程序中的所有堆函数映射成其调试版,这样可以获得带有源文件和行号的调试信息

第十章         调试多线程程序

1.         使用InterLocked系列加锁函数实现对单个整数变量的线程安全 *** 作

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对象会自己调用析构函数):

正确方法二(不让CWinThread对象自析构):

11.     在多个并发执行的线程中使用OutputDebugString函数输出调试信息可能使线程的执行串行化

12.     当跟踪一段可以被多个线程调用的代码时,你不能假设你现在所处的上下文就是你刚才所处的环境。可以通过观察局部变量的变化情况或者调用堆栈来判断上下文是否已经切换,还可以查看线程对话框来确定当前线程

13.     阻塞函数的超时时间是用真实时间来衡量的,而不是用有效CPU时间,中断程序后的时间也会记入超时时间,所以调试时经常会引起正常运行时不大可能发生的超时错误

14.     在调试多线程代码之前,先确定这个问题是否与版本、调试器、系统和处理器有关。从这些信息中你可以知道当看是使用调试器的时候应该先试些什么

15.     在出现错误的地方,适用线程对话框查看是否其它线程正与出错线程处理相同的数据

16.     使用dw( @TIB + 0x24 )来查看当前线程的ThreadID,就可以免于在线程对话框和代码窗口间频繁切换(在WinXP中不一定是这个值,待确定)

第十一章            COM调试

第十二章            非常规策略

from:http://blog.csdn.net/godenlove007/article/details/7989506

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/2088356.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-07-22
下一篇 2022-07-22

发表评论

登录后才能评论

评论列表(0条)

保存