4.1.1 使用ASSERT
ASSERT(ASSERT_VALID)宏仅在程序的“Debug”版本中捕捉程序错误。该宏在“Release”版本中不生成任何代码。
4.1.2 使用TRACE
以下的例子只能在debug中显示,
a) TRACE
CString csTest = “test”;
TRACE(“CString is %s/n”,csTest);
b) ATLTRACE
c) AfxDump
AfxDump要求被dump的对象从CObject类继承,并且实现了Dump的方法。
CTime time = CTime::GetCurrentTime();
#ifdef _DEBUG
afxDump << time << “/n”;
#endif
比如在下面的代码中,当nRet == 0 时就认为程序出错。但是如何定位此时i的值为几呢。
1) 将光标定位在要调试的语句前面
2) CTRL+B, 在Break at处选择 行号
3) 选择Condition
在[Enter the expression…] 输入 nRet == 0;点击OK。运行程序,在弹出报错对话框后点击retry,程序执行将停在断点处。 这是可以看到循环变量的数值比如i此时等于9。表明I == 9的时候出的错误。
在CTRL+B,在[Enter the number of times…]输入7,程序将在循环变量i = 8 时停下来。就可以进入出错的函数进行调试了。
见:
VCDebugSample/src/DebugMain/DebugMainDlg.cpp-----
void CDebugMainDlg::OnButtonSetBkpt()
4.1.4 数据断点(Data Breakpoint)
void CDebugMainDlg::OnButtonDataBkpt()
{
// TODO: Add your control notification handler code here
char szName1[10];
char szName2[4];
strcpy(szName1,"shenzhen"); //A
CString str1;
str1.Format("%s/n", szName1);
TRACE(str1);
strcpy(szName2, "vckbase"); //B
CString str2;
str2.Format("%s/n", szName2);
TRACE(str2);
str1.Format("%s/n", szName1);
TRACE(str1);#include "stdafx.h"
这段程序的输出是
sz1: shenzhe
sz21: vckbase
sz1: ase
szName1何时被修改呢?因为没有明显的修改szName1代码。我们可以首先在A行设置普通断点,F5运行程序,程序停在A行。然后我们再设置一个数据断点。如下图:
F5继续运行,程序停在B行,说明B处代码修改了szName1。B处明明没有修改szName1呀?但调试器指明是这一行,一般不会错,所以还是静下心来看看程序,哦,你发现了:szName2只有4个字节,而strcpy了7个字节,所以覆写了szName1。
数据断点不只是对变量改变有效,还可以设置变量是否等于某个值。譬如,你可以将Figure 2中红圈处改为条件”szName2[0]==''''y''''“,那么当szName2第一个字符为y时断点就会启动。
可以看出,数据断点相对位置断点一个很大的区别是不用明确指明在哪一行代码设置断点。
1 在call stack窗口中设置断点,选择某个函数,按F9设置一个断点。这样可以从深层次的函数调用中迅速返回到需要的函数。
2 Set Next StateMent命令(debug过程中,右键菜单中的命令)
此命令的作用是将程序的指令指针(EIP)指向不同的代码行。譬如,你正在调试上面那段代码,运行在A行,但你不愿意运行B行和C行代码,这时,你就可以在 D行,右键,然后“Set Next StateMent”。调试器就不会执行B、C行。只要在同一函数内,此指令就可以随意跳前或跳后执行。灵活使用此功能可以大量节省调试时间。
3 watch窗口
watch窗口支持丰富的数据格式化功能。如输入0x65,u,则在右栏显示101。
实时显示windows API调用的错误:在左栏输入@err,hr。
在watch窗口中调用函数。提醒一下,调用完函数后马上在watch窗口中清除它,否则,单步调试时每一步调试器都会调用此函数。
4 messages断点不怎么实用。基本上可以用前面讲述的断点代替。
DLL的测试与调试通常都要用到客户端,在客户端的调用DLL的API之前添加断点,可以直接进入到DLL内部调试。
通常的原因是由于被加载的dll同时加载了其他dll或者组件,当这些dll或者组件不存在时loadLibrary不会成功。可以使用Visual studio tools-〉depends, 将要加载的dll拖入到depends中,从里面可以找出那些dll或者组件不存在。
4.2.3 注册dll内部的COM组件时(Regsvr32)失败。
绝大多数失败也是由于加载dll不成功造成的。这是因为在注册dll内部的组件时,首先要加载此dll,如果加载失败,也会导致regsvr32失败,也可以采用上面的办法。
若要在 ATL 中跟踪引用数,请在包括 atlbase.h 之前添加以下代码行:
#define _ATL_DEBUG_INTERFACES
该语句导致在每次调用 AddRef 或 Release 时,“输出”窗口均显示接口的当前引用数以及对应的类名和接口名称。
可将断点设置于:void CDebugMainDlg::OnButtonTraceRefcnt()
若要在 ATL 中调试 QueryInterface 调用,请在包括 atlcom.h 之前添加以下定义:
#define _ATL_DEBUG_QI
然后在调试时,在“输出”窗口中查找在对象上查询的每个接口的名称。
可将断点设置于:void CDebugMainDlg::OnButtonTraceQI()
当涉及到多个线程通信时,要注意在正确的位置设置断点。
见:
void CDebugMainDlg::OnButtonProcThd()
void CDebugMainDlg::OnButtonWinThd()
void CDebugMainDlg::OnButtonThdMsg()
在例子中首先启动一个运行线程函数的线程(Thread1),启动一个CWinThread类型的线程(Thread2),之后在OnButtonThdMsg函数中使用event通知Thread1,当 Thread1接收到event之后,使用ThreadMessage通知Thread2。实际上还有一个窗口的主线程,也就是 OnButtonThdMsg运行的线程,所以要想全程根中点击button之后的运行情况,就要在各个线程中的恰当位置设置好断点。
在程序的调试状态,选择debug->Threads,可以查看当前运行的线程,并可以让某个线程挂起,继续执行等。