断点、单步运行
调试程序,最常用的方法是使用断点和单步运行,需要这个项目:
• 选择 Debug 方式编译
• 使用调试方式运行
设置和取消断点
• 点击要设置或取消断点的那一行程序上,按键盘的 F5 键设置或取消这行代码的断点
• 用鼠标点击程序代码行号左面的空白区域,也会设置和取消这行代码的断点
如果这行代码有断点,会在行号的左面,就是上面截图的位置,有红色的圆点作为断点标识。
程序在调试方式运行的时候,运行到这句代码的时候,会停下来,能看到此时变量的值。
有效的断点,无效的断点,禁用的断点
点击工具条上的 “” 或者按 F9 运行,下面是断点位置的截图:
| 无效的断点 |
| 当前运行到这,停在了这个断点上 |
| 这个位置没有断点,但是可以设断点的代码 |
| 有效的断点 |
无效的断点的原因是,这个位置没有程序代码,例如空行或注释,或者没有编译生成可执行的代码,例如定义变量。上面截图的无效断点就是只定义了变量,没有可执行的代码。
禁用断点:在断点列表里面禁用和启用断点。在断点列表里面还可以对断点进行设定进入断点的条件等。
查看和管理断点列表
• 选择菜单 View → Debug Windows → Breakpoints
• 使用快捷键 Ctrl + Alt + B
如下图,下面的 “Breakpoint List” 就是断点列表。
Filename/Address |
文件名(源码断点) 或地址 (地址断点),旁边的对钩是启用/禁用断点。如果把对钩去掉,断点就被禁用了,对应于源码上的断点也相应变灰,例如上图的第 129 行代码的断点。
| Line/Length |
行号 (源码断点) 或字节数 (数据断点)。
| Condition |
在这个断点停下来的条件。如果这里为空,没有输入条件,每次执行到这里的时候都停下来。如果输入了条件,当这个条件为真的时候,停在这个断点上。如上图,条件为 “i==5” ,当 i 的值等于 5 的时候,停在了断点上。
| Thread |
有多个线程调用这段代码时,在以调试方式运行的时候,可以选择希望在哪个线程里面执行这段代码时,停在这个断点上,而不去理会其他线程是否执行了这段代码。
| Action |
断点执行的动作,一般都是 “Break”,即程序运行到这里停下来
| Pass Count |
程序运行时,经过这个断点的次数。这里输入一个整数值,是希望程序运行的时候,经过几次这个断点停下来。
如上图:第 133 行断点,输入了 8 ,即 8 次停下来。现在停下来的时候,是第 5 次,Pass Count 里面显示的 5 of 8,现在停下来的原因,是在第 132 行断点停下来了,那里设定的条件是 i==5。再继续运行,即点击 “” 按钮,或按 F9 执行,会在第 8 次经过这个断点的时候停下来,此时 Pass Count 里面显示的是 8 of 8,此时的 i 值是 7,i 值从 0 到 7 正好是 8 次。 如下图:停在了第8次经过断点的地方。
| Group |
给断点分组,在这里输入分组的名字,会创建一个新的分组,或者加入已有的分组。也可以在下拉表里面选择已有的分组。 |
在调试程序的时候,一般都使用源码断点,即在源程序上设定的断点。地址断点和数据断点在 “查看和调试 CPU 反汇编代码” 的时候使用。
单步运行
程序停在断点上的时候,可以进行单步运行。
调试程序的工具条 |
Step Over | 快捷键 F8,工具条上的 “” 按钮,或者选择菜单 Run → Step Over,执行当前这一句程序代码,停在下一句程序代码上 |
Trace Into | 快捷键 F7,工具条上的 “” 按钮,或者选择菜单 Run → Trace Into,执行当前这一句程序代码,如果这句代码是调用函数,那么会跟踪进入函数,停在函数里面的第一句代码上,如果这句代码不是调用函数,会和 Step Over 一样停在下一句代码上 |
Trace to Next Source Line | 快捷键 Shift + F7,或者选择菜单 Run → Trace to Next Source Line,执行当前的代码,停在对应于源程序代码的下一句代码上。这个功能也会跟踪进入函数,和Trace Into 的区别在下面的 “查看和调试 CPU 反汇编代码” 里面描述 |
Run to Cursor | 快捷键 F4,或者选择菜单 Run → Run to Cursor,继续执行程序代码,一直到光标位置,停在光标位置的代码上,光标位置的这句代码没有设断点也会停在这里。 |
Run Until Return |
快捷键 Shift + F8,工具条上的 “” 按钮,或者选择菜单 Run → Run Until Return,执行当前函数里面的代码,一直到这个函数返回到调用位置,停在调用函数位置的下一句代码上 |
Program Pause | 工具条上的 “” 按钮,或者选择菜单 Run → Program Pause,程序暂停运行,停在正在执行的程序代码上,如果当前在库函数或Windows消息循环里面,没有源程序,会停在反汇编代码上 |
Program Reset |
快捷键 Ctrl + F2,工具条上的 “” 按钮,或者选择菜单 Run → Program Reset,这是复位,程序停止运行 |
Run |
快捷键 F9,工具条上的 “” 按钮,或者选择菜单 Run → Run,会继续执行,一直执行到停在下一个断点上。 |
查看和调试 CPU 反汇编代码
当程序运行停在了断点上,可以查看这个位置的反汇编代码,选择菜单 View → Debug Windows → CPU Windows → Disassembly,快捷键 Ctrl + Alt + D:
在上面截图里面可以看到,C/C++的程序代码的反汇编代码,每句 C/C++ 代码可能会对应一句或几句汇编代码。在 Disassembly 窗口里面,单步运行时:
Trace Into | 单步运行的时候,会执行一条汇编代码,停在下一句汇编代码上。如果是函数调用,会跟踪到函数里面,停在函数里面的第一条汇编代码上。如果调用的函数没有源程序,例如库函数或API函数,也会按照反汇编,停在这个函数的第一条反汇编代码上,如果继续跟踪,可能会跟踪很长时间都在库函数或API函数的源程序里面跑,这时候,如果再想跟踪回源代码位置,需要用Trace to Next Source Line停在下一句执行到的源程序代码上对应的反汇编上。 |
Trace to Next Source Line | 会执行一条或几条汇编代码,停在下一句 C/C++ 程序代码对应的第一条汇编代码上。如果有函数调用,并且调用的这个函数对应了 C/C++ 源程序,会停在函数里面第一条C/C++源程序对应的汇编代码上。有的函数是没有源程序的,例如库函数或 Windows API 函数等,如果没有源程序也会跟踪到函数里面,但是不停下来,会一直执行到找到源程序位置,比如这个函数有产生事件或调用 CALLBACK函数,也会停在事件或CALLBACK函数里面,停在执行到的下一条源程序代码对应的汇编代码上。 |
其他的 |
Stop Over、Run to Cursor、Run Until Return、Run等,都和前面调试 C/C++ 源程序代码的一样。 |
查看断点或单步运行时的变量的值
鼠标悬停
停在断点时,鼠标停留在变量上,可以看到变量的值,例如下面截图里面的 v 的值
Local Variables
在左面的 Local Variables 里面,能看到局部变量的值
Watch List
Watch List 除了可以查看变量值之外,还支持表达式,例如上面截图的 a + b,值为 5,数组的值,可以看到下面截图的 v,数组 v 的值可以点击 “+” 号查看更多的值。还有另外的查看数组的方法,可以设定查看数组里面某个值或某个值开始的几个值,例如下面截图的v[3]开始的2个值: 6 和 8。
在 Watch List 里面,点击鼠标右键,选择 Add Watch 添加
Expression | 变量或表达式 |
Group name | 显示在 Watch List 里面的分组名,可以自己随意取一个名字作为新的分组名,分组会显示在 Watch List 下面的分页卡上,用以切换显示的分组 |
Repeat count | 从表达式那个值开始,显示的值个数,一般都是数组使用,例如上面的截图里面,是显示从 v[3] 开始的2个值,即v[3] 和 v[4] 的值 |
Digits | 如果表达式的值是浮点数,这里是浮点数显示的位数 |
Enabled | 启用/禁用这个Watch值,打勾为启用 |
Use visualizer | 把不可读的变量值显示为可读的值,按照Embarcadero的文档提示,主要是影响日期和时间类型,如果这个不打勾,显示出来的值是一个浮点数,不知道具体表示的日期和时间是什么,如果打勾了,可以看到日期和时间的值,例如 “2016-05-06 16:25:32”。默认情况,这个是打勾的。 |
Allow side effects and function calls | 如果表达式里面包含函数调用, 是否调用函数去计算表达式的值。默认情况,这个是不打勾的,表达式需要调用函数才能计算出来的时候,显示的值会为无法计算。 |
下面有9个单选项 | 是把变量或计算的值显示为什么样的类型,默认情况,选择Default会自动识别,多数情况不需要修改。 |
删除 Watch List 里面的表达式:鼠标右键点击到要删除的项目上,选择 Delete Watch。
修改 Watch List 里面的表达式:鼠标右键点击到要修改的项目上,选择 Edit Watch。
输出信息到 C++ Builder 的 Event Log 区域
函数 OutputDebugString(字符串); 可以把字符串输出到 Event Log 区域,只要执行到了这个函数就会输出,无论是 Debug 方式编译还是 Release 方式编译。
有时候不希望程序停下来,只是想看看程序是否执行到了代码的某个位置,或者想看看程序执行的流程顺序是否正确,可以用这个方法。
OutputDebugString 的参数是字符串,用起来不是很方便,可以做这样的一个定义:
#define TRACE(s...) OutputDebugString(String().sprintf(s).c_str()) |
这样,TRACE 就和 printf 的参数一样了,支持格式化输出到 Event Log 区域。
下面就是 TRACE 的例子和执行结果,TRACE 格式化输出到 Event Log 区域:
使用日志文件
写一个简单的写文件的函数,这个函数在文件末尾添加文字,支持格式化输出的参数:
执行以下程序代码
执行结果:打开 "D:\\Test.log" 文件,内容如下:
使用 CodeGuard
CodeGuard可以发现程序里面的内存泄漏和访问越界等问题。
目前 C++ Builder 10 版本,只有 Borland Win32 编译器可以使用 CodeGurad,
clang 编译器,包括 clang Win32 和 clang Win64 还都不能使用 CodeGuard。
选择菜单 Project → Options,在左面找到 C++ Compiler → Debugging,右面的 Enable CodeGuard就是这个选项,打勾选True,为启用这个功能,不打勾选False,为不启用这个功能,默认是不打勾,不启用这个功能。
如果启用了 CodeGuard,程序必须带CodeGuard调试的运行库才能运行,如果要发布程序,需要把这个去掉,重新编译。
下面的例子包含访问越界和内存泄漏:
当程序执行到光标位置就停下来,并且 CodeGuard 在 Messages 区域提示:
访问越界,在 Unit1.cpp 的 131 行,
访问4个字节在0x0267590+40,就是在偏移为0+40的位置,内存只分配了40个字节
这个数组是使用 new[] 分配的40个字节的内存。
如上图所示:程序结束的时候,CodeGurad 又在 Messages 区域增加了两行提示信息:
资源泄漏,在 Unit1.cpp 的第 129 行,
数组没有被 delete,
数组是使用 new[] 分配的 40 个字节。
程序结束的时候,在 exe 文件所在的文件夹里面,有一个和 exe 同名的 .cgl 文件,是CodeGuard创建的日志文件,可以用记事本打开查看。
例 Project1.cgl 的内容如下:
|