VC调试技巧

一、 引言

本文主要介绍以下几方面内容:

常见编译错误
VC调试器
VC快捷键
VC项目文件说明
环境参数的设置

二、常见编译错误 

1. Fatal Error C1010                        
unexpected end of file while looking for precompiled header directive
这一般是由于使用了参数/Yu“stdafx.h”,意思是在每个文件中都应该使用#include来包含这个头文件。一般改正,就是在每个CPP文件中包含这个文件就可以。
l 2. Error C2065            
 undeclared identifier
调用的方法或变量没有定义。
l 3. warning C4700
local variable ‘p’ used without having been initialized
使用的变量未经初始化。
l 4. LNK2001
unresolved external symbol “symbol”(不确定的外部“符号”)。
如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或标签,将产生此错误消息。一般来说,发生错误的原因有两个:
一是所引用的函数、变量不存在、拼写不正确或者使用错误;
其次可能使用了不同版本的连接库。

三、VC调试器

1. 调试环境的建立 

在VC中每当建立一个工程(Project)时,VC会为你建立两个版本,Release版本,和Debug版本(默认)。
Release版本是当程序完成后,准备发行时用来编译的版本;
Debug版本是用在开发过程中进行调试时所用的版本。
Debug 版本当中,包含着Microsoft格式的调试信息,不进行任何代码优化,而在Release版本对可执行程序的二进制代码进行了优化,其中不包含任何的调试信息。
在新建立的工程中,你所看到是Debug版本,若要选择Release版本,可以选择菜单Project中的Setting命令,这时屏幕上面弹出Project Setting对话框,在Setting For下拉列表中选择Release。

                                                                                           vc 图

                                                                                           vs2005 图

2. 断点(breakpoint)

分类:在VC中,你可以设置多种类型的断点,我们可以根据断点的性质把断点分为三类:

(1)与位置有关的断点(F9)

1) 与位置有关的逻辑断点——到点条件成立才断
    有的时候你可能并不需要程序每次运行到这儿都停下来,而是在满足一定条件的情况下才停下来,这时你就需要设置一种与位置有关的逻辑断点。要设置这种断点我们只需要在Edit中选中Breakpoint项,则弹出Breakpoint对话框,选其中的Location标签,在Location页面中单击Condition按钮,在Expression编辑框中写出你的逻辑表达式,如X>=3或a+b>25,最后按OK返回。这种断点主要是由其位置发生作用的,但也结合了逻辑条件,使之更灵活。如图:

2) 在在汇编代码上设立断点:

  有时我们需要进入程序的汇编代码,更深入地调试程序,因此要在汇编代码上设立断点。要设立这种断点我们只需选择Debug Window中的Disassembly子命令,这时汇编窗口将会出现在屏幕上。在汇编窗口中你将看到对应于源程序的汇编代码,其中源程序是用黑体字显示,下面是且对应的汇编代码。要设立断点,我们只需将光标移到你想设断点处然后点击工具条上的Insert/Remove Breakpoints 按钮,此后你将会看到一个红圆点出现在该汇编代码的右边。
(2)与逻辑条件有关的断点;
1)逻辑条件触发断点的设置 ;
– 选中Breakpoint对话框中的DATA标签;
– DATA页面中的Expression编辑框中写出你的逻辑表达式,如(6==sum);


l 2)监视表达式发生变化断点;
 – 选中Breakpoint对话框中的DATA标签;
 – 在Expression编辑框中写出你需要监视的表达式;
l 3)监视数组发生变化的断点
– 选中Breakpoint对话框中的DATA标签;
– 在Expression编辑框中写出你需要监视数组名;
– 在Number of Elements 编辑框输入你需要监视数组元素的个数;

l 4)监视由指针指向的数组发生变化的断点;
– 选中Breakpoint对话框中的DATA标签;
– 在Expression编辑框中输入形如*pointname,其中*pointname为指针变量名;
– 在Number of Elements 编辑框输入你需要监视数组元素的个数;
l  5)监视外部变量发生变化的断点;
– 选中Breakpoint对话框中的DATA标签;
– 在Expression编辑框中输入变量名;
– 点击在Expression编辑框的右边的下拉键头;
– 选取Advanced选项,这时Advanced Breakpoint 对话框出现;
– 在context框中输入对应的函数名和(如果需要的话)文件名;
(3)与WINDOWS消息有关的断点;
l 注意:此类断点只能工作在x86 或 Pentium 系统上。
– 选中Breakpoint对话框中的Message标签;
– 在Break At WndProc 编辑框中输入Windows 函数的名称;
– 在Set One Breakpoint From Each Message To Watch下拉列表框中选择对应的消息;

应用断点:如何控制程序的运行
当我们从菜单Build到子菜单Start Debuging 选择Go 程序开始运行在Debug状态下,程序会由于断点而停顿下来后,可以看到有一个小箭头,它指向即将执行的代码。随后,我们就可以按要求来控制程序的运行:其中有四条命令:Step over, step Into , Step Out ,Run to Cursor 。
Step over 的功能是运行当前箭头指向的代码(只运行一条代码)。F10
Step Into的功能是如果当前箭头所指的代码是一个函数的调用,则用Step Into 进入该函数进行单步执行。F11
Step Out的功能是如当前箭头所指向的代码是在某一函数内,用它使程序运行至函数返回处。Shift F11
Run to Cursor的功能是使程序运行至光标所指的代码处。Ctrl F10

3. VC调试器——查勘现场 

l 查看工具的使用
    调试过程中最重要的是要观察程序在运行过程中的状态,这样我们才能找出程序的错误之处。这里所说的状态包括各变量的值,寄存中的值,内存中的值,堆栈中的值 ,为此我们需要利用各种工具来帮助我们察看程序的状态。

先看一下这张图,然后再看下面的介绍:

(1) 弹出式调试信息泡泡(Data Tips Pop_up Information)
当程序在断点停下来后,要观察一个变量或表达式的值的最容易的方法是利用调试信息泡泡。要看一个变量的值,只需在源程序窗口中,将鼠标放到该变量上,你将会看到一个信息泡泡弹出,其中显示出该变量的值。要查看一个表达式的值,先选中该表达式,仍后将鼠标放到选中的表达式上,同样会看到一个信息泡泡弹出以显示该表达式的值。
l
(2)变量窗口(VARIABLE WINDOW)——看程序中的变量
断点处或其附近被访问的变量的当前值。Variables窗口的下部有三个标签:
AUTO:显示变量和函数返回值;
LOCAL:显示当前函数的局部变量;
THIS:在一个C++程序中,显示this指针当前指向的对象;
(3)观察窗口(WATCH WINDOW)——索要变量或表达式的当前值
被调试器直接跟踪的变量和表达式的当前值。在Watch窗口中指定那些你在程序暂停时总想知道他们当前值的那些变量。
在观察窗口中双击Name栏的某一空行,输入你要查看的变量名或表达式,回车后你将会看到对应的值。观察窗口可有多页,分别对应于标签Watch1、Watch2、Watch3。假如你输入的表达式是一个结构或是一个对象,你可以用鼠标点取表达式右边的形如 + ,以进一步观察其中的成员变量的值。
(4)快速查看变量对话框(quick watch)
在快速查看变量对话框中你可以象利用观察窗口一样来查看变量或表达式的值。但我们还可以利用它来改变运行过程中的变量,具体操作如下:
1)在Debug 菜单,选择Quick Watch命令,这时屏幕上将会出现Quick Watch 对话框;
2)在Expression 编辑框中输入变量名,按回车;
3)在Current Value 格子中将出现变量名及其当前对应的值;
4)如要改变该变量的值只需双击该变量对应的Name 栏,输入你要改变的值;
5)如要把该变量加入到观察窗口中,点击Add watch 按钮;
6)点击Close 按钮返回;

Shift F9 如图:

(5)查看内存中的值
一个指定地址的内存堆。
在Memory窗口中,在Address 编辑框中输入你要查看的内存地址,对应内存地址中的值将被显示出来;

(6)查看或改变CPU寄存器中的值
1) 在Registers 窗口中,信息以 Register = Value 的形式显示,其中Register 代表寄存器的名字,Value 代表寄存器中的值;
2)如果你要修改某一个寄存器的值,用TAB键或鼠标将光标移到你想改变的值的右边,然后输入你想要的值。
l 在寄存器中,有一类特殊的寄存器称为标志寄存器,其中有八个标志位:
OV是溢出标志
UP是方向标志
EI是中断使能标志
Sign 是符号标志
Zero是零标志
Parity是奇偶较验标志
Carry 是进位标志

(7)查看Call Stack
在Call Stack窗口中可看到还未返回的调用函数列表,调用栈给出从嵌套函数调用一直到断点位置的执行路径。
(8)查看Disassembly窗口
编译代码的汇编语言翻译,补充道屏幕上的源窗口中。“Disassembly”指的是把程序中的机器代码转换为相应的汇编指令。

四、VC快捷键 

1. VC编辑快捷键

按下Alt  键不放,点击鼠标左键拖动,可以选择文本块、可选择列; 
按着Ctrl键不放,单击一个单词,可以选择一个单词,或双击;
将光标移在开始位置,按住shift点击鼠标左键可选择一段(在IE浏览其中照样可用,看不到光标而已);
双击鼠标左键可选择一个单词; 
按住shift+上下方向键可选择行; 
按住ctrl+shift+左右方向键可选择一个单词;
按Ctrl+C可COPY光标所在的这一行;
按住shift+[End]可选择本行;
F3                 向下  
Tab         选择的行全部右移一个 TAB键的宽度
Alt + F8           按定义的格式重新排列选定的文本。 
shift + F3         向上 
shift + Tab        选择的行全部左移一个 TAB键的宽度 
Ctrl + F           查找、搜索 
Ctrl + H           替换 
Ctrl + G           到某行
Ctrl + U           选择部分变为小写
Ctrl + shift + U   选择部分变为大写
Ctrl+J                向上搜索最近的#if/#else/#ifdef/#endif  
Ctrl+K             向下搜索最近的#if/#else/#ifdef/#endif
Ctrl+]             自动配对大括号或小括号。但有时不对应,是因为其他字符有“{”或“}”存在

2. VC调试快捷键

F5          Go    运行碰到断点就停
F7          Build    编译链接
F9          Add/Remove Breakpoint 插入/删除断点 
F10         Step Over             一步步运行,碰到函数不进去 
F11         Step Into            一步步运行,碰到函数就进去(当然那些WinAPI由于在Dll中,就进不去了!)
Ctrl + F5   Execute Program       运行
Ctrl + F7   Compile               编译一个源文件
Ctrl + F10  Run to Cursor         调试到光标所在位置
Shift + F5  Stop Debugging        停止调试     
Shift + F9  Quick Watch           快速查看/修改变量信息
Shift + F11 Step Out              从当前函数中跳出
Alt + F9    Breakpoint            高级断点设置
Alt + 7     Call Stack            堆栈窗口,可以察看函数调用情况
Alt + 4     Variables             当前运行代码行的变量或者返回值信息
Alt + 3     Watch                可以把关注的变量拖入窗口中,察看/修改变量信息。

五、VC项目文件说明

.opt工程关于开发环境的参数文件,如工具条位置等信息;    
.dsp(DeveloperStudio Project)项目文件,文本格式,项目参数配置文件,不熟悉的话不要手工修改;
.dsw(DeveloperStudio Workspace)是工作区文件,其他特点和dsp差不多,可以由.dsp生成;
.plg是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大,在Tools->Options里面有个选项可以控制这个文件的生成; 
以上是我们工程编译时候最常见的,下边这些可以不用关心,了解就可以了。
.aps(AppStudio File),资源辅助文件,二进制格式,一般不用去管他;
.clw  ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下,有时候ClassWizard出问题,手工修改CLW文件可以解决,如果此文件不存在的话,每次用ClassWizard的时候会提示你是否重建;
.hpj(Help Project)是生成帮助文件的工程,用microsfot Help Compiler可以处理;
.mdp(Microsoft DevStudio Project)是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的DSP格式;
.map是执行文件的映像信息纪录文件,除非对系统底层非常熟悉,这个文件一般用不着;
.pch(Pre-Compiled  File)是预编译文件,可以加快编译速度,但是文件非常大;
.pdb(Program Database)记录了程序有关的一些数据和调试信息,在调试的时候可能有用;
.exp只有在编译DLL的时候才会生成,记录了DLL文件中的一些信息,一般也没什么用;
注:如果你想与别人共享你的源代码项目,但是把整个项目做拷贝又太大。你完全可以删掉以下文件: 
 .dsw、.ncb、.opt、.aps、.clw、. plg文件以及Debug、Release目录下的所有文件

六、环境参数的设置(Project Setting —> Alt F7)

VC的处理流程,大致分为两步:
编译:源文件通过预编译和编译生成了.obj
              文件;
链接:所有.obj文件和.lib文件通过链接生成
              .exe文件或.dll文件;
下面,我们分别讨论这两个步骤的一些细节。
l Project->Settings->C/C++
Project Option中各个参数代表的意义,可以参考MSDN,比如/nologo表示编译时不在输出窗口显示这些设置(我们可以把这个参数去掉来看看效果)等等。
一般我们不会直接修改这些设置,而是通过这一页最上面的Category中的各项来完成。
General
Warning level
控制警告信息,其中Level1是最严重的级别
Warnings as errors
将警告信息当作错误处理
Optimizations
代码优化,可以在Category的Optimizations项中进行更细的设置
Generate browse info
用以生成.sbr文件,记录类、变量等符号信息,可以在Category的Listing Files项中进行更多的设置
Debug info
生成调试信息
None
不产生任何调试信息(编译比较快)
Line Numbers Only
仅生成全局的和外部符号的调试信息到.OBJ文件或.EXE文件,减小目标文件的尺寸
C7 Compatible
记录调试器用到的所有符号信息到.OBJ文件和.EXE文件
Program Database
创建.PDB文件记录所有调试信息
Program Database for Edit and Continue
创建.PDB文件记录所有调试信息,并且支持调试时编辑
C++ Language

pointer_to_member representation
用来设置类定义/引用的先后关系,一般为Best-Case Always表示在引用类之前该类肯定已经定义了
Enable Exception Handling
进行同步的异常处理
Enable Run-Time Type Information
迫使编译器增加代码在运行时进行对象类型检查
Disable Construction Displacements
设置类构造/析构函数调用虚函数问题
l Code Generation
Processor
表示代码指令优化,可以为80386、80486、Pentium、Pentium Pro,或者Blend表示混合以上各种优化

 

 

Use run-time library
用以指定程序运行时使用的运行时库(单线程或多线程,Debug版本或Release版本),有一个原则就是,一个进程不要同时使用几个版本的运行时库,连接了单线程库就不支持多线程调用,连接了多线程库就要求创建多线程的应用程序
Single-Threaded
静态连接LIBC.LIB库
Debug Single-Threaded
静态连接LIBCD.LIB库
Multithreaded
静态连接LIBCMT.LIB库
Debug Multithreaded
静态连接LIBCMTD.LIB库
Multithreaded DLL
动态连接MSVCRT.DLL库
Debug Multithreaded DLL
动态连接MSVCRTD.DLL库
Calling convention
用来设定调用约定,有三种:__cdecl、__fastcall和__stdcall。各种调用约定的主要区别在于,函数调用时,函数的参数是从左到右压入堆栈还是从右到左压入堆栈;在函数返回时,由函数的调用者来清理压入堆栈的参数还是由函数本身来清理;以及在编译时对函数名进行的命名修饰(可以通过Listing Files看到各种命名修饰方式)
Struct member alignment
用以指定数据结构中的成员变量在内存中是按几字节对齐的,根据计算机数据总线的位数,不同的对齐方式存取数据的速度不一样。这个参数对数据包网络传输等应用尤为重要,不是存取速度问题,而是数据位的精确定义问题,一般在程序中使用#pragma pack来指定
Customize

Disable Language Extensions
表示不使用微软为标C做的
Eliminate Duplicate Strings
主要用于字符串化(将字符串放到充池里以省空),使用个参数,使得
char *sBuffer = "This is a character buffer";
char *tBuffer = "This is a character buffer";
sBuffer和tBuffer指向的是同一内存空
Enable Function-Level Linking
诉编译器将各个函数按打包格式编译
Enables minimal rebuild
保存关联信息到.IDB文件,使编译器只最新动过的源文件行重编译,提高编译速度
Enable Incremental Compilation
.IDB文件保存的信息,只重编译最新改动过的函数
Suppress Startup Banner and Information Messages
用以控制参数是否在output窗口
Listing Files
Generate browse info
上面已经提到过,这里可以进行更多的设置
Exclude Local Variables from Browse Info
是否将局部变量的信息放到.SBR文件中
Listing file type
可以设置生成的列表信息文件的内容
Assembly-Only Listing
仅生成汇编代码文件(.ASM扩展名)
Assembly With Machine Code
生成机器代码和汇编代码文件(.COD扩展名)
Assembly With Source Code
生成源代码和汇编代码文件(.ASM扩展名)
Assembly, Machine Code, and Source
生成机器码、源代码和汇编代码文件(.COD扩展名)
Listing file name
生成的信息文件的路径,一般为Debug或Release目录下,生成的文件名自动取源文件的文件名
Optimizations
Maximize Speed
生成最快速的代码
Minimize Size
生成最小尺寸的程序
Customize
定制优化
Assume No Aliasing
不使用别名(提高速度)
Assume Aliasing Across Function Calls
仅函数内部不使用别名
Global Optimizations
全局优化,比如经常用到的变量使用寄存器保存,或者循环内的计算优化,如
i = -100;while( i < 0 ){i += x + y;}
会被优化为i = -100;t = x + y;
while( i < 0 ){i += t;}
Generate Intrinsic Functions
使用内部函数替换一些函数调用(提高速度)
Improve Float Consistency
浮点运算方面的优化
Favor Small Code
程序(exe或dll)尺寸优化优先于代码速度优化
Favor Fast Code
程序(exe或dll)代码速度优化优先于尺寸优化
Frame-Pointer Omission
不使用帧指针,以提高函数调用速度
Full Optimization
组合了几种参数,以生成最快的程序代码
Inline function expansion
内联函数扩展的三种优化(使用内联可以节省函数调用的开销,加快程序速度)
Disable
不使用内联
Only __inline
仅函数定义前有inline或__inline标记使用内联
Any Suitable
除了inline或__inline标记的函数外,编译器“觉得”应该使用内联的函数,都使用内联
l Precompiled Headers
预编译头文件的设置。使用预编译可以提高重复编译的速度。VC一般将一些公共的、不大变动的头文件(比如afxwin.h等)集中放到stdafx.h中,这一部分代码就不必每次都重新编译(除非是Rebuild All) 
l Preprocessor

预编译处理。可以定义/解除定义一些常量,Additional include directories可以指定额外的包含目录,一般是相对于本项目的目录,如..Include。 
l Project->Settings->Link


 General
一些总体设置,可以设置生成的文件路径、文件名、连接的库文件。
Generate debug info
生成Debug信息到.PDB文件(具体格式可以在Category->Debug中设置)
Ignore All Default Libraries
放弃所有默认的库连接
Link Incrementally
通过生成. ILK文件实现递增式连接以提高后续连接速度,但一般这种方式下生成的文件(EXE或DLL)较大
Generate Mapfile
生成.MAP文件记录模块相关信息
Enable Profiling
这个参数通常与Generate Mapfile参数同时使用,而且如果产生Debug信息的话,不能用.PDB文件,而且必须用Microsoft Format。
l Customize
这里可以进行使用程序数据库文件的设置。
Force File Output:强制产生输出文件(EXE或DLL);
Print Progress Messages:可以将连接过程中的进度信息输出到Output窗口;
l Debug
设置是否生成调试信息,以及调试信息的格式。
格式可以有Microsoft Format、COFF Format(Common Object File Format)和Both Formats三种选择;Separate Types表示将Debug格式信息以独立的.PDB文件存放,还是直接放在各个源文件的.PDB文件中。选中的话,表示采用后者的方式,这种方式调试启动比较快。
l Input
这里可以指定要链接的库文件,放弃链接的库文件。还可以增加额外的库文件目录,一般是相对于本项目的目录,如..Lib。
Force Symbol References可以指定连接特定符号定义的库。
l Output
l Base Address可以改变程序默认的基地址(EXE文件默认为0×400000,DLL默认为0×10000000)。操作系统装载一个程序时总是试着先从这个基地址开始。
l Stack allocations,用以设置程序使用的堆栈大小(请使用十进制),默认为1兆字节。Version Information告诉连接器在EXE或DLL文件的开始部分放上版本号。
Entry-Point Symbol
可以指定程序的入口地址,一般为一个函数名(且必须采用__stdcall调用约定)。一般Win32的程序,EXE的入口为WinMain,DLL的入口为DllEntryPoint;最好让连接器自动设置程序的入口点。默认情况下,通过一个C的运行时库函数来实现:控制台程序采用mainCRTStartup (或wmainCRTStartup)去调用程序的main (或wmain)函数;Windows程序采用WinMainCRTStartup (或 wWinMainCRTStartup)调用程序的WinMain
(或 wWinMain,必须采用__stdcall调用约定);DLL采用_DllMainCRTStartup调用DllMain函数(必须采用__stdcall调用约定)。
其他一些参数的设置
(1)Project->Settings->General,可以设置连接MFC库的方式(静态或动态)。如果是动态连接,在你的软件发布时不要忘了带上MFC的DLL。
(2)Project->Settings->Debug,可以设置调试时运行的可执行文件,以及命令行参数等。
(3)Project->Settings->Custom Build,可以设置编译/连接成功后自动执行一些操作。比较有用的是,写COM时希望VC对编译通过的COM文件自动注册,可以如下设置:
Description: Register COM
Commands: regsvr32 /s /c $(TargetPath)
echo regsvr32 exe.time > $(TargetDir)$(TargetName).trg
Outputs: $(TargetDir)$(TargetName).trg
(4)Tools->Options->Directories,设置系统的Include、Library路径。
l注:值得注意的是,上面各个参数是大小写敏感的;在参数后加上“-”表示该参数无效;各个参数值选项有“*”的表示为该参数的默认值;可以使用页右上角的“Reset”按钮来恢复该页的所有默认设置。

七、总结

VC提供了一个很好的Debug工具,其提供的调用栈、条件断点、数据断点、反汇编等工具足够强大,足够应付平常的Bug,程序员不仅应具有对Bug的定位能力,更为主要的还是对于调试工具的掌握、使用的能力。所谓“磨刀不误砍柴工”,在开发之前或者开发闲暇时,好好的研究一下一些开发、调试工具不失为提升这种能力的好办法。能静下心来思考一下这些工具的工作原理就更好了,这样不仅能帮助你在编程的时候预见Bug,并且对你提高你的编程技巧也会有所帮助。
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页