<?xml version="1.0" encoding="GB2312" ?>
<?xml-stylesheet type="text/xsl" href="../../article.xsl" ?>

<article>

<title>VC补遗之Debug篇</title>

<author>晨光（Morning）</author>

<keywords>
  <keyword>VC</keyword>
  <keyword>Debug</keyword>
  <keyword>调试技巧</keyword>
</keywords>

<from type="原作"/>
<copyright/>


<paragraph name="引子">
前阵子因为工作的需要，翻阅了《Visual C++ 6宝典》一书。虽然自己接触VC也有些年头了，可却发现里面也有不少内容是我鲜有了解的，以下是我摘录并整理的部分内容，希望会对经常使用VC却有着和我一样情况的朋友有所帮助，本文取名“补遗”也正是出于此意。当然，这里所选的条目，或许稍有偏向，因为毕竟是从自己的角度出发，摘录了自认为容易忽略的内容。如果，你需要了解此处未有提及的其他内容，请查阅相关书籍。
</paragraph>

<paragraph name="调试应用程序前所要做的必要设置">
Project Settings->C/C++选项卡->General Category
<list>
  <li>Debug info选择Program Database for Edit and Continue</li>
  <li>Optimizations选择Disable(Debug)</li>
  <li>若不选择Generate browse info，可以节省编译时间</li>
</list>
</paragraph>

<paragraph>
Project Settings->C/C++选项卡->Code Generation Category
<list>
  <li>Use run-time library选择Debug库（比如：Debug Multithreaded DLL）</li>
</list>
</paragraph>

<paragraph>
Project Settings->Link选项卡->General Category
<list>
  <li>选择Generate debug info</li>
</list>
</paragraph>

<paragraph>
如果调试DLL，则需要：Project Settings->Debug选项卡->General Category
<list>
  <li>Executable for debug session填入加载和访问DLL的程序的名称（比如：为了调试ActiveX控件，你可以使用VC的实用程序tstcon32.exe）</li>
</list>
</paragraph>

<paragraph name="Variables窗口">
Variables窗口包含三个选项卡。Auto选项卡显示运行程序当前行以及上一行中所使用的变量；Locals选项卡显示在当前执行的函数内具有局部定义的所有变量（包括局部变量和传入函数的参数）；this选项卡显示this指针当前所指向的对象内部的成员变量
</paragraph>

<paragraph>
你可以通过Variable窗口修改简单类型变量的当前值（比如：int、double），只要在窗口中双击该值就可以修改了。
</paragraph>

<paragraph name="Watch窗口">
可以在Watch窗口中手工将表达式输入到Name域中，或者拖拽表达式，或者从剪贴板粘贴。Watch窗口也可以修改简单类型的变量的值。
</paragraph>

<paragraph name="Memory窗口">
可以查开所有调试进程的地址空间中的内存内容。可以按字节格式、字格式和双字格式显示内存内容。为显示具体位置处的存储内容，要在Memory窗口的Address栏内键入表达式。Memory窗口可能显示指定地值之前的内存内容，不过它会将光标定位在表达式对应的地址处。
</paragraph>

<paragraph name="Disassembly窗口">
Set Next Statement功能可以更改处理器的指令指针，以为处理器选择需要执行的下一条指令。但是，如果你设置的语句在另一个函数中或者你不能正确维护堆栈，则后果是不可预测的，通常正在调试的程序会崩溃。
</paragraph>

<paragraph name="调试多线程">
调试多线程应用程序时，可以通过Debug菜单（调试运行时才可见）下的Threads功能，打开Threads对话框，从而将焦点设置给程序内的一个具体线程。还可以挂起、恢复某个线程
</paragraph>

<paragraph name="调试异常">
通过Debug菜单（调试运行时才可见）下的Exceptions功能，打开Exceptions对话框，指定调试运行时，程序对异常的响应情况（包括：Stop always和Stop if not handled两种）。
</paragraph>

<paragraph name="简单调试技巧">
- 使用AfxMessageBox
</paragraph>

<paragraph>
如果调试程序窗口的出现干扰了应用程序的执行，或者错误出现在release版程序中，则可以使用AfxMessageBox输出你所感兴趣的信息。这类似于早先用printf输出调试信息的方法。
</paragraph>

<paragraph>
- 使用TRACE宏：
</paragraph>

<paragraph>
MFC提供了宏TRACE、TRACE0、TRACE1、TRACE2、TRACE3，用以生成调时输出。它们将调试信息输出到afxDump对象，该变量是MFC的CDumpContext类的一个实例，它将信息发送到Output窗口。尽量使用TRACE0~TRACE3，因为它们需要的存储空间小于TRACE。TRACE0输出一个字符串，TRACE1输出字符串并可附带一个变量，TRACE2可以附带两个变量，TRACE3可以附带三个。在创建Unicode应用程序时，TRACE必须使用_T宏格式化字符串，而TRACEn不需要。release版应用程序将忽略所有的TRACE和TRACEn宏。
</paragraph>

<paragraph>
- 使用断言：
</paragraph>

<paragraph>
除了ASSERT外，ASSERT_VALID宏可以验证一个指向CObject派生类对象的指针的有效性。release版应用程序将忽略所有的ASSERT和ASSERT_VALID宏。
</paragraph>

<paragraph>
- 使用Dump
</paragraph>

<paragraph>
CObject类中包含Dump()成员函数，它将内容通过afxDump对象输出到Output窗口。你可以在派生类中重载它。 
</paragraph>

<paragraph>
- 内存泄漏：
</paragraph>

<paragraph>
可以使用CMemeoryState检测内存泄漏。方法：
<item>在你感兴趣的那段代码之前，创建CMemeoryState对象</item>
<item>调用Checkpoint函数</item>
<item>在你感兴趣的那段代码之后，调用DumpAllObjectsSince函数</item>
</paragraph>

<paragraph>
如果不需要完全dump在两次调用Checkpoint之间被分配的所有对象，则：
<item>在你感兴趣的那段代码之前，创建CMemeoryState对象msBeforeCall, msAfterCall, msDiffBA</item>
<item>调用msBeforeCall的Checkpoint函数</item>
<item>在你感兴趣的那段代码之后，调用msAfterCall的Checkpoint函数</item>
<item>调用msDiffBA的Difference函数对msBeforeCall和msAfterCall进行比较</item>
<item>若两者存在差异，则调用msDiffBA的DumpStatistics</item>
</paragraph>

<paragraph>
无法使用CMemoryState对malloc/free，GlobalAlloc/GlobalFree，LocalAlloc/LocalFree进行检测。
</paragraph>

<paragraph>
- 使用MFC Tracer
</paragraph>

<paragraph>
MFC Tracer(文件名：tracer.exe)，可以启用或禁用部分或全部MFC跟踪调试信息（TRACE和TRACEn宏）。
</paragraph>

<paragraph name="远程调试">
远程调试允许你调试运行在与你机器相连的其他机器上的程序。远程机器必须运行Visual C++ Debug Monitor（Msvcmon.exe，和MSDEV.exe在同一个目录下），为了运行Msvcmon.exe，远程机器上必须有：
<em>Msvcrt.dll、Tln0t.dll、Dm.dll、Msvcp60.dll、Msdis100.dll</em>。步骤：
<list>
<li>在远程机器上运行Msvcmon.exe，选择Network(TCP/IP)，选择Settings设置目标机器的名称，再选择Connect</li>
<li>在远程机器上运行要调试的程序</li>
<li>在本地机器上运行Visual Studio，选择Build->Debugger Remote Connection打开Remote Connection，选择Network(TCP/IP)，选择Settings设置目标机器的名称</li>
<li>按F5开始调试程序</li>
</list>
</paragraph>

<paragraph>
远程调试的主要优点是应用程序运行在一个不会因为出现调试程序而受影响的机器上，适合于调试涉及显示、键盘、音频驱动等程序。同时，也适合于调试debug版能正常运行而release版不能正常运行的程序。
</paragraph>

<paragraph>
(待续)
</paragraph>

</article>