调试_调试工具 - CSDN
精华内容
参与话题
  • 如何调试代码

    千次阅读 2018-02-22 15:54:45
    近几日老哥在家指导着写了挺多代码,调试代码作为一个必经的过程也有了更深刻的了解。借这篇博客整理一下。(下文举例编程软件为VS) 调试bug最好用的是打断点。大一学习时并没有在意过断点的用法,每次调代码都是...

    近几日老哥在家指导着写了挺多代码,调试代码作为一个必经的过程也有了更深刻的了解。借这篇博客整理一下。(下文举例编程软件为VS)

    调试bug最好用的是打断点。大一学习时并没有在意过断点的用法,每次调代码都是直接正面刚,肉眼找错误,直到寒假才真正熟练掌握了断点的使用方法(捂脸)。在某处语句前打上断点,调试时便会在这里停止。下面举一个简单的例子:
    代码
    在第7行打上断点后,调试时程序会执行到while之前。一方面可以检测一下whlie语句之前是否有错误,另一方面可以通过逐步执行观察各个变量的数值变化。
    逐步执行
    下面可以显示出变量的监测窗口,方便实时对照
    变量
    逐步执行定位问题是很简单的一种途径。

    注释掉部分语句也是定位问题的一种方式。不过适用于整块函数。

    展开全文
  • C语言调试

    万次阅读 多人点赞 2016-05-10 07:33:40
    1.调试的概念以及调试器的选择 在编写代码的过程中,相信大家肯定遇到过这样的情况:代码能够编译通过,没有语法错误,但是运行结果却不对,反复检查了很多遍,依然不知道哪里出了问题。这个时候,就需要调试程序了...

    1.调试的概念以及调试器的选择

    在编写代码的过程中,相信大家肯定遇到过这样的情况:代码能够编译通过,没有语法错误,但是运行结果却不对,反复检查了很多遍,依然不知道哪里出了问题。这个时候,就需要调试程序了。

    所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。

    在调试的过程中,我们可以监控程序的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。

    编译器可以发现程序的语法错误,调试可以发现程序的逻辑错误。所谓逻辑错误,是指代码思路或者设计上的缺陷。

    对于初学者来说,学习调试也可以增加编程的功力,它能让我们更加了解自己自己的程序,比如变量是什么时候赋值的、内存是什么时候分配的,从而弥补学习的纰漏。

    调试是每个程序员必须掌握的技能,没有选择的余地!

    调试器(Debugger)

    调试需要借助专业的辅助软件——调试器(Debugger)。现在主流C/C++调试器有下面几种:

    1) Remote Debugger

    Remote Debugger 是 VC/VS 自带的调试器,与整个IDE无缝衔接,使用非常方便,初学者建议使用该调试器,本教程也以 VS2010 为例讲解调试技巧。

    2) WinDbg

    大名鼎鼎的 Windows 下的调试器,它的功能甚至超越了 Remote Debugger,它还有一个命令行版本(cdb.exe),但是这个命令行版本的调试器指令比较复杂,不建议初学者使用。

    3) LLDB

    XCode 自带的调试器,Mac OS X 下开发必备调试器。

    4) GDB

    Linux 下使用最多的一款调试器,也有 Windows 的移植版,如果你不使用 VC/VS,GDB 将是一个不错的选择。

    考虑到大部分读者使用Windows,本教程以 VS2010 为例讲解调试技巧,也即使用 Remote Debugger 调试器。当然你也可以使用 VS2012、VS2013、VS2015 等,它们之间的差别很小。


    2.设置断点,开始调试

    默认情况下,程序不会进入调试模式,代码会瞬间从开头执行到末尾。要想观察程序的内部细节,就得让程序在某个地方停下来,我们可以在这个地方设置断点。

    所谓断点(BreakPoint),可以理解为障碍物,人遇到障碍物不能行走,程序遇到断点就暂停执行。


    上图中,我们希望让程序在第4行代码处暂停执行,那么在第4行代码左侧的灰色部分单击鼠标即可插入断点。你也可以将光标定位到要暂停的代码行,然后按F9键插入断点。也可以在要暂停的位置单击鼠标右键,在弹出菜单中插入断点,如下图所示:

    插入断点后,点击上方的“运行”按钮,或者按F5键,即可进入调试模式,如下图所示:

    可以看到,程序虽然运行了,但并未输出任何数据,这是因为在输出数据之前就暂停了。同时,在IDE的上方出现了与调试相关的工具条,下方也多出了几个与调试相关的窗口:
    • 调用堆栈可以看到当前函数的调用关系。
    • 断点窗口可以看到当前设置的所有断点。
    • 即时窗口可以让我们临时运行一段代码,后续我们会重点讲解。
    • 输出窗口和我们之前看到的没有,用来显示程序的运行过程,给出错误信息和警告信息。
    • 自动窗口会显示当前代码行和上一代码行中所使用到的变量。
    • 局部变量窗口会显示当前函数中的所有局部变量。
    • 线程模块窗口暂时无需理会。

    如果你的VS缺少某个窗口,可以通过VS上方的“调试”菜单调出,如下图所示:
    注意:必须在调试状态下才能看到图中的菜单。
    如果你希望关闭某个窗口,可以在窗口标题处单击鼠标右键,在弹出菜单中隐藏,如下图所示:

    断点的真正含义

    严格来说,调试器遇到断点时会把程序暂时挂起,让程序进入一种特殊的状态——中断状态,这种状态下操作系统不会终止程序的执行,也不会清除与程序相关的元素,比如变量、函数等,它们在内存中的位置不会发生变化。

    关键是,处于中断状态下的程序允许用户查看和修改它的运行状态,比如查看和修改变量的值、查看和修改内存中的数据、查看函数调用关系等,这就是调试的奥秘。

    继续执行程序

    点击“运行”按钮或者按F5键即可跳过断点,让程序恢复正常状态,继续执行后面的代码,直到程序结束或者遇到下一个断点。

    在调试过程中,按照上面的方法可以设置多个断点,程序在执行过程中每次遇到断点都会暂停,如下图所示:

    删除断点

    如果不希望程序暂停,可以删除断点。删除断点也很简单,在原有断点处再次单击鼠标即可,也可以将光标定位到要删除断点的代码行,再次按F9键,或者在右键菜单中删除,如下图所示

    代替暂停语句

    在VS下,程序运行结束后不会自动暂停(一闪而退),要手动添加暂停语句system("pause");,如果大家觉得麻烦,也可以在代码最后插入断点,强制程序暂停。


    3.查看和修改变量的值

    设置了断点,就可以观察程序的运行情况了,其中很重要的一点就是查看相关变量的值,这足以发现大部分逻辑错误。

    将下面的代码复制到源文件中:

    1. #include <stdio.h>
    2. int main(){
    3. int value_int, array_int[3];
    4. float value_float;
    5. char* value_char_pointer;
    6. //在这里插入断点
    7. value_int = 1048576;
    8. value_float = 2.0;
    9. value_char_pointer = "Hello World";
    10. array_int[0] = 379; array_int[1] = 94;
    11. //在这里插入断点
    12. return 0;
    13. }
    在第7行和第12行插入断点。运行到第一个断点时,在局部变量窗口可以看到各个变量的值:

    可以看到,未经初始化的局部变量和数组的值都是垃圾值,是随机的,没有意义。双击变量的值,可以进行修改。

    点击“运行”按钮或按F5键,程序会运行到下一个断点位置,在局部变量窗口可以看到各个值的变化:

    更加快捷的方式

    除了在窗口中查看变量,还有一种更加便捷的方法:在调试模式下,把鼠标移动到要查看的变量的上方,即可看他它的值。如下图所示:

    如果是数组、指针、结构体等还可以展开,如下图所示:
    这种查看变量的方式在实际开发中使用很多。

    添加监视

    如果你希望长时间观测某个变量,还可以将该变量添加到监视窗口。在要监视的变量处单击鼠标右键,弹出如下菜单:

    选择“添加监视”,在VS下方的监视窗口就可以看到当前变量:

    这样,每次变量的值被改变都会反映到该窗口中,无需再将鼠标移动到变量上方查看其值。尤其是当程序稍大时,往往需要同时观测多个变量的值,添加监视的方式就会显得非常方便。


    4.单步调试(逐语句调试和逐过程调试)

    在实际开发中,常常会出现这样的情况,我们可以大致把出现问题的代码锁定在一定范围内,但无法确定到底是哪条语句出现了问题,该怎么办呢?按照前面的思路,必须要在所有代码行前面设置断点,让代码一个断点一个断点地执行。

    这种方案确实可行,但很麻烦,也不专业,这节我们就来介绍一种更加便捷的调试技巧——单步调试。所谓单步调试,就是让代码一步一步地执行。

    下面的代码用来求一个等差数列的和,我们以该代码来演示单步调试:

    1. #include <stdio.h>
    2. int main(){
    3. int start, space, length, i, thisNum;
    4. long total = 0;
    5. printf("请输入等差数列的首项值:");
    6. scanf("%d", &start);
    7. printf("请输入等差数列的公差:");
    8. scanf("%d", &space);
    9. printf("请输入等差数列的项数:");
    10. scanf("%d", &length);
    11. for(i=0; i<length; i++){
    12. thisNum = start + space * i;
    13. if( length-i > 1 ){
    14. printf("%d + ", thisNum);
    15. }else{
    16. printf("%d", thisNum);
    17. }
    18. total += thisNum;
    19. }
    20. printf(" = %ld\n", total);
    21. return 0;
    22. }
    在第6行设置一个断点,然后点击“逐过程调试”按钮,或者按F10键,程序就会运行到下一行并暂停:

    再次点击“逐过程”按钮或按F11键,就会执行第7行代码,要求用户输入数据。用户输入结束后,黄色箭头就会指向第8行,并暂停程序。如此重复执行上面的操作,就可以让程序一条语句一条语句地执行,以观察程序的内部细节,这就称为单步调试

    逐过程调试(F10)和逐语句调试(F11)

    刚才我们在第6行设置了断点,按下“逐过程”按钮或F10键,程序就会执行 printf(),并暂停在下一行代码处。

    printf() 是一个函数,它本身由多条语句构成,如果你希望进入 printf() 函数内部,查看整个函数的执行过程,可以点击“逐语句”按钮,或者按F11键,这样程序就会进入 printf() 所在的源文件,如下图所示:

    当需要跳出函数时,可以点击“跳出”按钮,或者按Shift+F11键,就会返回刚才的代码。

    逐过程(F10)逐语句(F11)都可以用来进行单步调试,但是它们有所区别:
    • 逐过程(F10)在遇到函数时,会把函数从整体上看做一条语句,不会进入函数内部;
    • 逐语句(F11)在遇到函数时,认为函数由多条语句构成,会进入函数内部。

    逐语句(F10)不仅可以进入库函数的内部,还可以进入自定义函数内部。在实际的调试过程中,两者结合可以发挥更大的威力。

    断点 + 查看/修改变量 + 逐过程调试 + 逐语句调试,这足以解决绝大多数逻辑问题,到此,初学者就已经学到了调试的基本技能。

    修改代码运行位置

    在VS中,调试器还允许我们直接跳过一段代码,不去执行它们。将下面的代码复制到源文件:
    1. #include <stdio.h>
    2. int main(){
    3. printf("111\n");
    4. printf("222\n");
    5. printf("333\n");
    6. printf("444\n");
    7. printf("555\n");
    8. printf("666\n");
    9. return 0;
    10. }
    在第3行设置断点,开始单步调试。假设我们不希望执行4~6行的代码,那么当程序执行到第4行时,可以将鼠标移动到黄色箭头处,直接向下拖动到第7行,如下图所示:
    程序执行完成后,在控制台上会输出:
    111
    555
    666

    注意:随意修改程序运行位置是非常危险的行为,假设我们定义了一个指针,在第N行代码中让它指向了一个数组,如果我们在修改程序运行位置的时候跳过了第N行代码,并且后面也使用到了该指针,那么就极有可能导致程序崩溃。


    5.即时窗口的使用

    “即时窗口”是VS提供的一项非常强大的功能,在调试模式下,我们可以在即时窗口中输入C语言代码并立即运行,如下图所示:


    在即时窗口中可以使用代码中的变量,可以输出变量或表达式的值(无需使用printf()函数),也可以修改变量的值。

    即时窗口本质上是一个命令解释器,它负责解释我们输入的代码,再由VS中的对应模块执行,最后将输出结果呈现到即时窗口。

    需要注意的是,在即时窗口中不能定义新的变量,因为程序运行时 Windows 已经为它分配好了只够刚好使用的内存,定义变量是需要额外分配内存的,所以调试器不允许在程序运行的过程中定义变量,因为这可能会导致不可预知的后果。

    调用函数

    在即时窗口中除了可以使用代码中的变量,也可以调用代码中的函数。将下面的代码复制到源文件中:
    1. int plus(int x, int y){
    2. return x + y;
    3. }
    4. int main(){
    5. return 0;
    6. }
    在第6行设置断点,并在即时窗口中输入plus(5, 6),如下图所示:


    6.查看、修改运行时的内存
    在 Visual Studio 的调试过程中,有时候我们可能需要对程序的运行内存进行查看,修改该怎么办?Visual Studio 也为我们提供了很好的解决方案。那就是使用 Visual Studio 自带的内存查看窗口。

    首先我们通过内存窗口查看变量的值,我们启动 Visual Studio,创建一个工程,输入如下代码:
    1. #include <stdio.h>
    2. int main()
    3. {
    4. int testNumber = 5678;
    5. printf("testNumber 的内存地址为 0x00%x \n", &testNumber);
    6. //输出内存地址
    7. //TODO:在这里插入断点
    8. return 0;
    9. }
    我们在第七行设置好断点,然后按 F5 启动调试,等待触发断点。触发断点后,我们发现,IDE中并没有显示内存窗口(默认设置下),这时,我们点击菜单 -> 调试(D) -> 窗口 (W) -> 内存 (M) -> 内存1(1),就可以调出内存窗口了,如图:

    我们看到,内存窗口里面显示着一大堆乱七八糟的数据,这里面的这些就是我们内存中的数据啦,我们可以通过变量 testNumber 的内存地址跳转到器对应的内存空间,我们看到 testNumber 的地址为 0x0018f830 (在不同的进程是不一样的),我们把这个地址输入到我们的内存窗口的地址栏。如图:

    我们看到,尽管我们已经输入了正确地地址,但是我们还是没有看到正确的数据显示,其实原因非常简单,我们来回顾一下 C 语言的一些入门知识:我们知道,在我们的源代码中,我们的 testNumber 变量被定义为 int 整形,我们再想想 int 整形在内存中占几个字节?没错,是4个字节。所以我们应该以四字节的形式格式化这些内存数据,这是我们在内存窗口中单击我们的鼠标右键,在弹出的菜单中选择“4字节整数(4)”,然后就能正确地显示相关的数据了,如图:

    没错,查看内存就是这么的简单。接下来我们就来查看与修改浮点数的内存数据,我们看下面这段代码:
    1. #include <stdio.h>
    2. int main()
    3. {
    4. double pi = 3.141592653589;
    5. printf("pi 的内存地址为 %x \n", &pi);
    6. //输出内存地址
    7. //TODO:在这里插入断点
    8. return 0;
    9. }
    同样的,我们在第7行设置断点,按F5启动调试,等待断点被触发:

    这时我们看到的内存地址是这样的,与我们在内存窗口看到的不同,我们需要将其补齐,在我们现阶段编写的小程序中,显示的内存地址基本上都是六位的,我们在前面加上 “0x00”,将其补到八位(内存窗口上的地址栏里有几位就补到几位)。然后我们将其输入到内存窗口上的地址栏。

    我们发现,现在显示的数据依然是错误的,因为查看器现在还是在使用我们之前设置的 4位整形格式 格式化我们的内存数据呢,我们知道,我们的 double 属于64位浮点数,所以我们在查看窗口点击鼠标右键,在弹出的菜单中选择“64位浮点(6)”,然后我们就能看到它正确地输出数据了。

    我们注意到,在我们设置的变量pi的值的基础上,内存窗口里显示的数据多了几个0,这些0所代表的就是 double 型数据的最高精度。接下来我们尝试在内存窗口修改变量 pi 的值,为其补齐精度。现在我们用鼠标右键点击我们的pi的内存数据,在弹出的菜单中选择编辑值(E),在显示的输入框中我们输入 3.1415926535897931,按回车即可保存。我们看看效果:

    怎么样,内存的查看与修改是不是很简单呢?其实我们只要记住下面的几个对应关系,常用的数值数据类型的内存查看与修改都不在话下:
    类型名 变量类型 内存查看窗口中应选择的数据格式
    short 16 位整形 2 字节整数
    int 32 位整形 4 字节整数
    long 32 位整形 4 字节整数
    long long 64 位整形 8 字节整数
    float 32 位(4字节)单精度浮点数 32 位浮点
    double 64 位(8字节)双精度浮点数 64 位浮点
     
    内存新手常用的内存窗口操作就到这里了,其实关于内存操作还有很多高级用法,不过那不在我们初级调试教程的范围之内。我们在修改内存的时候要注意安全,防止随意修改导致的程序崩溃,甚至是无法结束进程!

    7.有条件断点的设置

    在此之前,我们已经了解了无条件断点、跟踪点这两种断点,事实上在 Visual Studio 中还有几种常用的断点,在本节我们将一一为大家介绍。

    大家有没有碰到这样的情况,在一个循环体中设置断点,假设有一千次循环,我们想在第五百次循环中设置断点,这该怎么办?反正设置断点不断按 F5 继续运行四百九十九次是不可能的。那该怎么办呢?其实我们的断点是可以设置各种各样的条件的,对于上述情况,我们可以对断点的命中次数做一个限制。

    我们首先在 Visual Studio 中创建一个工程,并且输入如下代码:

    1. #include <stdio.h>
    2. int main(){
    3. for ( int i=1 ; i <= 1000 ; i++ ) {
    4. //TODO:插入计次断点
    5. printf("我真行!\n");
    6. }
    7. }
    首先,我们在第4行插入断点,分析代码,我们不难得出它会输出 1000 行“我真行!”,那么我们思考一下,在不修改代码的情况下,如何才能让他输出 1499 行“我真行!”呢,其实很简单,我们只要在i 等于500的时候暂停程序,再将变量 i 的值修改为 1 即可,思路很简单,接下来我们就来实现这个命中条件的限制吧。

    首先我们用鼠标右键单击第4行的断点图标,在弹出的菜单中选择 命中次数(H) ,接下来会弹出如下图的一个对话框,我们在中间的选择框中选择 “中断,条件是命中次数等于”,我们在右边的编辑框输入 500。

    我们点击确定,断点就设置到位了,接下来我们按 F5 运行调试。

    我们看到,在输出四百九十九行“我真行!”后,程序进入了中断状态,这是我们注意到自动窗口中的变量 i 的值为 500,接下来我们把这个 i 的值改为 1,点击 继续(C) 继续程序的运行,这样程序就再输出了一千行“我真行!”,然后退出。没错,命中次数限制的使用就是这么简单。

    我们再次用鼠标右键单击第4行的断点图标,在弹出的菜单中选择 命中次数(H) ,大家如果有兴趣的话,可以试试中间的选择框中其他的条件选项,使用方法基本一致,这里不再赘述。

    接下来我们来了解一下断点触发条件的使用,在 Visual Studio 的调试器中,我们可以对断点设置断点触发条件,这个条件可以引用我们程序中的变量,比如我们程序中有两个变量 a、b ,我们的命中条件可以是 a == b 、 a >= b 、 a != b 甚至是 (a - b)*(a*2 - b) > 0 这样的复杂条件。

    下面我们就来试试在 Visual Studio 中插入条件断点吧。我们首先创建一个工程,输入如下代码:
    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <time.h> //time函数所在头文件
    4. int main()
    5. {
    6. int a, b;
    7. int randNumber;
    8. srand((unsigned)time(NULL));
    9. //设置随机数种子,以产生不同随机数
    10. for (int i = 0; i<50; i++)
    11. {
    12. a = rand() % 7; //产生0-6的随机数
    13. b = rand() % 7; //产生0-6的随机数
    14. //TODO:在这里插入条件断点: a == b
    15. }
    16. return 0;
    17. }
    我们让程序运行过程中 a 等于 b 的时候触发断点,首先,我们在第十四行插入断点,然后我们鼠标右键单击左侧的断点图标,在弹出的菜单中选择条件(C),IDE会弹出如下对话框,我们在条件输入框中输入 a==b ,然后在下面选择 为 true ,然后点击确定即可。


    8.assert断言函数

    在我们的实际开发过程之中,常常会出现一些隐藏得很深的BUG,或者是一些概率性发生的BUG,通常这些BUG在我们调试的过程中不会出现很明显的问题,但是如果我们将其发布,在用户的各种运行环境下,这些程序可能就会露出马脚了。那么,如何让我们的程序更明显的暴露出问题呢?这种情况下,我们一般都会使用 assert 断言函数,这是C语言标准库提供的一个函数,也就是说,它的使用与操作系统平台,调试器种类无关。我们只要学会了它的使用,便可一次使用,处处开花。

    接下来我们来了解一下 assert 函数的用法,这个函数在 assert.h 头文件中被定义,在微软的 cl 编译器中它的原型是这样的:

    #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )

    我们看到 assert 在 cl 编译器中被包装成了一个宏,实际调用的函数是 _wassert ,不过在一些编译器中,assert 就是一个函数。为了避免这些编译器差异带来的麻烦,我们就统一将 assert 当成一个函数使用。

    我们来了解一下 assert 函数的用法和运行机制,assert 函数的用法很简单,我们只要传入一个表达式即可,它会计算我们传入的表达式的结果,如果为真,则不会有任何操作,但是如果我们传入的表达式的计算结果为假,它就会像  stderr (标准错误输出)打印一条错误信息,然后调用 abort 函数直接终止程序的运行。

    现在我们在 Visual Studio 中创建一个工程,输入下面这段非常简单的代码:
    1. #include <stdio.h>
    2. #include <assert.h>
    3. int main()
    4. {
    5. printf("assert 函数测试:");
    6. assert(true); //表达式为真
    7. assert(1 >= 2); //表达式为假
    8. return 0;
    9. }
    我们按 F5 启动调试:

    我们看到,我们的输出窗口打印出了断言失败的信息,并且 Visual Studio 弹出了一个对话框询问我们是否继续执行。但是如果我们不绑定调试器,构建发布版程序,按 Ctrl + F5 直接运行呢?是的,这个 assert 语句就无效了,原因其实很简单,我们看看 assert.h 头文件中的这部分代码:
    1. #ifdef NDEBUG
    2. #define assert(_Expression) ((void)0)
    3. #else /* NDEBUG */
    我们看到,只要我们定义了 NDEBUG 宏,assert 就会失效,而 Visual Studio 的默认的发布版程序编译参数中定义了 NDEBUG 宏,所以我们不用额外定义,但是在其他编译器中,我们在发布程序的时候就必须在包含 assert.h 头文件前定义 NDEBUG 宏,避免 assert 生效,否则总是让用户看到“程序已经停止运行,正在寻找解决方案 . . .”的 Windows 系统对话框可就不妙了。

    下面我们来了解一下 assert 的常用情境以及一些注意事项。举个C语言文件操作的例子:
    1. #include <stdio.h>
    2. #include <assert.h>
    3. #include <stdlib.h>
    4. int main(void)
    5. {
    6. FILE *fpoint;
    7. fpoint = fopen("存在的文件.txt", "w");
    8. //以可读写的方式打开一个文件
    9. //如果文件不存在就自动创建一个同名文件
    10. assert(fpoint);
    11. //(第一次断言)所以这里断言成功
    12. fclose(fpoint);
    13. fpoint = fopen("不存在的文件.txt", "r");
    14. //以只读的方式打开一个文件,如果不存在就打开文件失败
    15. assert(fpoint);
    16. //(第二次断言)所以断言失败
    17. printf("文件打开成功");
    18. //程序永远都执行不到这里来
    19. fclose(fpoint);
    20. return 0;
    21. }
    阅读代码,我们可以知道,如果我们错误的设置了读写权限,或者没有考虑 Windows 7(或更高版本) 的 UAC(用户账户权限控制) 问题的话,我们的程序出现的问题就很容易在我们的测试中暴露出来。事实上,上面的代码也不是一定能通过第一次断言的,我们把构建生成的(Debug 模式的)可执行文件复制到我们的系统盘的 Program Files 文件夹再执行,那么 Win7 及以上的操作系统的UAC都会导致程序无法通过第一次断言的。所以在这种情况下我们就需要在必要的情况申请管理员权限避免程序BUG的发生。

    在我们的实际使用过程中,我们需要注意一些使用 assert 的问题。首先我们看看下面的这个断言语句:
    //...
    assert( c1 /*条件1*/ && c2 /*条件2*/ );
    //...
    我们思考一下:如果我们的程序在这里断言失败了,我们如何知道是 c1 断言失败还是 c2 断言失败呢,答案是:没有办法。在这里我们应该遵循使用 assert 函数的第一个原则:每次断言只能检验一个条件,所以上面的代码应该改成这样:
    //...
    assert(c1 /*条件1*/);
    assert(c2 /*条件2*/);
    //...
    这样,一旦出现问题,我们就可以通过行号知道是哪个条件断言失败了。

    接下来我们来思考一个问题:我们可以使用 assert 语句代替我们的条件过滤语句么?我们来举个例子,我们写了一个视频格式转换器,我们本应输出标准MP4格式的视频,但是由于编码解码库的问题,我们在转换过程中出现了错误,像这种情况下,我们能否用 assert 代替掉我们的 if 语句呢(假设这里忽略了 Visual Studio 发布版程序编译过程中自动定义的 NDEBUG 宏)?很明显,这样做是不可以的。这是为什么呢?

    现在我们来区分一下程序中的错误与异常,所谓错误,是代码编写途中的缺陷导致的,是程序运行中可以用 if 语句检测出来的。而异常在我们的 C 语言中,一般也可以使用 if 语句判断,并进行对应的处理。而 assert 是用来判断我们程序中的代码级别的错误的。像用户输入错误,调用其他函数库错误,这些问题我们都是可以用 if 语句检测处理的。另一方面,使用 if 语句的程序对用户更友好。

    下面我们通过代码展示使用 assert 的另外一个注意意事项,我们新建一个工程,输入如下代码:
    1. #include <stdio.h>
    2. #include <assert.h>
    3. int main(void)
    4. {
    5. int i = 0;
    6. for ( ; ; )
    7. {
    8. assert(i++ <= 100);
    9. printf("我是第%d行\n",i);
    10. }
    11. return 0;
    12. }
    我们按 F5 运行调试器,我们会看到这样的情景:

    这是正常的,我们按 Shift + F5 终止调试,接下来,我们切换一下编译模式到发布模式:

    接下来我们按 Ctrl+F5 不绑定调试器直接运行:

    我们看到了一个完全不相同的运行结果,这是为什么呢?其实原因很简单,我们注意这段代码:

    assert(i++ <= 100);

    我们的条件表达式为 i++ <= 100,这个表达式会更改我们的运行环境(变量i的值),在发布版程序中,所有的 assert 语句都会失效,那么这条语句也就被忽略了,但是我们可以把它改为 i++ ; assert(i <= 100); ,这样程序就能正常运行了。所以请记住:不要使用会改变环境的语句作为断言函数的参数,这可能导致实际运行中出现问题。

    最后,我们再来探讨一下,什么时候应该用 assert 语句?一个健壮的程序,都会有30%~50%的错误处理代码,几乎用不上 assert 断言函数,我们应该将 assert 用到那些极少发生的问题下,比如Object* pObject = new Object,返回空指针,这一般都是指针内存分配出错导致的,不是我们可以控制的。这时即使你使用了容错语句,后面的代码也不一定能够正常运行,所以我们也就只能停止运行报错了。


    9.调试信息输出

    上一节,我们讲解了 assert 断言函数的使用,本节我们来学习在调试器的调试窗口上输出我们自己的调试信息,在这里,我们将用到一个 Windows 操作系统提供的函数 —— OutputDebugString,这个函数非常常用,他可以向调试输出窗口输出信息(无需设置断点,执行就会输出调试信息),并且一般只在绑定了调试器的情况下才会生效,否则会被 Windows 直接忽略。接下来我们了解一下这个函数的使用方法。

    首先,这个函数在 windows.h 中被定义,所以我们需要包含 windows.h 这个头文件才能使用 OutputDebugString 函数。这个函数的使用方式非常的简单,它只有简单的一个参数——我们要输出的调试信息。但是有一点值得注意:准确来说 OutputDebugString 并不是一个函数,他是一个宏。在高版本的 Visual Studio 中,因为编译的时候 Visual Studio 默认定义了 UNICODE 宏,所以我们查找 OutputDebugString 的定义会看到如下代码:

    1. #ifdef UNICODE
    2. #define OutputDebugString OutputDebugStringW
    3. #else
    4. #define OutputDebugString OutputDebugStringA
    5. #endif // !UNICODE
    我们可以从代码高亮上看到,OutputDebugString 实际上等价于 OutputDebugStringW,这就意味着我们必须传入宽字符串(事实上只要定义了 UNICODE ,调用所有 Windows 提供的函数都需要使用宽字符),或者使用 TEXT 或 _T 宏,并且这是最好的方法,这个宏会自动识别编译器是否处于默认宽字符的状态并对传入字符串做些处理,使用这个宏可以加强代码对不同编译器不同编译参数的兼容性。下面我们就来看一段示例代码:
    1. #include <windows.h> //使用 OutputDebugString 包含此文件
    2. #include <tchar.h> //使用 TEXT 宏需要包含此文件
    3. int main(){
    4. OutputDebugString(TEXT("你好,C语言中文网。"));
    5. OutputDebugString(_T("大家好才是真的好。"));
    6. //也可以:OutputDebugStringA("大家好才是真的好。");
    7. //也可以:OutputDebugStringW(L"大家好才是真的好。");
    8. //使用自动字符宏 TEXT 或者 _T 可以自动判断是否使用宽字符
    9. system("pause"); //使程序暂停一下
    10. return 0;
    11. }
    在程序执行 system("pause"); 暂停的时候我们来观察一下我们的,调试输出窗口:

    怎么样,是不是输出了“你好,C语言中文网。大家好才是真的好。”呢?这个函数与 printf 等函数一样,需要我们自己插入换行符,但在 Windows 下,我们一般使用 \r\n 作为完整的换行符。

    直接使用这个调试信息输出函数有个弊端,那就是它不能直接输出含参数的字符串。但是我们可以通过 sprintf / wsprintf 等缓冲区写入函数来间接实现输出含参数字符串的功能。下面是一段示例代码:
    1. #include <stdio.h>
    2. #include <windows.h>
    3. int main(){
    4. //注意!这段代码我们指定使用ANSI字符!
    5. char szBuffer[200];
    6. int number = 100;
    7. sprintf_s(szBuffer, "变量 number 的值是 %d \r\n", number); //写入缓冲区,注意不要溢出
    8. OutputDebugStringA(szBuffer);
    9. sprintf_s(szBuffer, "变量 number 的地址是 %x \r\n", &number);
    10. OutputDebugStringA(szBuffer);
    11. //我门指定使用 ANSI 版本的 OutputDebugString 函数
    12. return 0;
    13. }
    我们按 F5 开始调试:

    我们看到了输出的结果,怎么样,大家是不是觉得这样调用这个函数很麻烦?为解决此问题,这里C语言中文网为大家提供了一个更好的解决方案,我们可以自己写一个前端函数,然后保存到头文件中(编译生成 dll 也可以,有兴趣的同学可以试试)。为了方便,我们已经编写好了这么一套函数。代码如下:
    1. #include <stdio.h>
    2. #include <windows.h>
    3. #ifndef _DEBUG_INFO_HEADER_
    4. //防止头文件被重复载入出错
    5. #define _DEBUG_INFO_HEADER_
    6. #if (defined UNICODE)||(defined _UNICODE)
    7. #define DebugInfo DebugInfoW
    8. #else
    9. #define DebugInfo DebugInfoA
    10. #endif
    11. // 函数: DebugInfoA(char*, int, ...)
    12. //
    13. // 目的: 以窄字符的形式输出调试信息
    14. //
    15. // char* str - 格式化 ANSI 字符串
    16. // ... - 任意不定长参数
    17. //
    18. void DebugInfoA(char* str, ...){
    19. char szBuffer[500]; //注意不要让缓冲区溢出!
    20. va_list Argv;
    21. va_start(Argv, str);
    22. _vsnprintf_s(szBuffer, 500, str, Argv);
    23. va_end(Argv);
    24. OutputDebugStringA(szBuffer);
    25. }
    26. // 函数: DebugInfoW(char*, int, ...)
    27. //
    28. // 目的: 以宽字符的形式输出调试信息
    29. //
    30. // char* str - 格式化 UNICODE 字符串
    31. // ... - 任意不定长参数
    32. //
    33. void DebugInfoW(wchar_t* str, ...){
    34. wchar_t szBuffer[1000];
    35. va_list Argv;
    36. va_start(Argv, str);
    37. _vsnwprintf_s(szBuffer, 500, str, Argv);
    38. va_end(Argv);
    39. OutputDebugStringW(szBuffer);
    40. }
    41. #endif
    上面的这段代码会自动识别编译器是否默认使用了宽字符并且使用对应版本的输出函数,其中注释为 Visual Studio 的智能提示信息,我们把上面的代码保存到 debuginfo.h 并添加到当前工程中,就可以直接通过如下代码调用:
    1. #include <stdio.h>
    2. #include <tchar.h>
    3. #include <windows.h>
    4. #include "debuginfo.h"
    5. int main(){
    6. int num;
    7. //这里我们使用微软提供的 xxxx_s 安全函数
    8. printf_s("请输入数值:\n");
    9. scanf_s("%d", &num);
    10. DebugInfo(TEXT("用户输入的数是 %d\n"), num);
    11. return 0;
    12. }
    我们按 F5 开始调试,我们输入666,观察 IDE 的输出窗口:

    我们可以看到,它正确地输出了 “用户输入的数是 666” ,这就说明我们的代码执行成功了,上面的代码大家可以随意修改我们上面提供的代码以符合自己的实际需要。但是请注意不要让缓冲区溢出,否则会造成不可估计的后果。

    我们除了在调试器中可以看到调试字符串的输出,我们还可以借助 Sysinternals 软件公司研发的一个相当高级的工具 —— DebugView 调试信息捕捉工具,这个工具可以在随时随地捕捉 OutputDebugString 调试字符串的输出(包括发布模式构建的程序),可以说这是个神器,大家可以在微软 MSDN 库上搜索下载。接下来我们运行 DebugView 调试信息捕捉工具。

    首先我们打开 DebugView:

    我们可以看到随时有程序在输出调试信息,接下来我们运行(按Ctrl+F5 运行,不要启动调试器)刚才的程序,输入 222,看看 DebugView 有什么反应:

    这个调试信息查看工具显示了正确的调试信息。注意!如果挂载了 Visual Studio 的调试器,这个工具就会失效。

    使用这个工具可以捕获发布版程序的输出,我们一般可以用来跟踪测试产品的运行状态。以趁早发现我们程序中存在的问题,避免发布后出现的容易被检测到的BUG。


    10.VS调试的总结以及技巧

    我们已经对 Visual Studio 的调试有了一些了解,在这一节,我们将向大家展示我们在调试过程中可以使用的一些实用技巧。使大家的调试操作更加轻松愉快。

    首先我们再了解一下Visual Studio 中,Release构建模式和Debug 构建模式的区别。我们在可以切换构建模式。Release构建模式下构建的程序为发行版,而Debug构建模式下构建的程序为调试版。在 Visual Studio 中调试模式还会定义两个宏 _DEBUG 和 DEBUG,后文我们将介绍它们的一些妙用。在 Visual Studio 中,如果我们要更改编译参数的话,可以点击菜单 -> 项目(P) -> <项目名>属性(P),我们在弹出的页面左侧选择配置属性即可对编译参数进行修改。

    接下来,我们来了解一下调试标记。不知道大家有没有遇到这样的情况,我们需要在调试的时候额外运行一段代码,但是实际发布的时候却不需要这段代码呢。那该怎么办,绝大多数数的初学者会选择使用注释,即在发布的时候将无用的测试代码注释掉。但是这样很麻烦,下面我们就为大家介绍一种全新的方法——使用调试标记。事实上这种方法我们在前面使用过,但是没有详细讲解。

    这种方法借助了预处理指令,方法很简单,我们首先定义一个宏作为处于调试状态的标记,后面的代码我们用 #ifdef 和 #endif 预处理指令检测宏是否被定义,然后由编译器判断是否编译其中的代码。这么做的好处就是可以减少发布程序的体积,另一方面可以提高发布程序的运行效率。下面是一段示范代码:

    1. #include <stdio.h>
    2. #define _DEBUGNOW
    3. int main(){
    4. #ifdef _DEBUGNOW
    5. printf("正在为调试做准备...");
    6. #endif // _DEBUGNOW
    7. printf("程序正在运行...");
    8. return 0;
    9. }
    当我们要发布上面的这个程序的时候,我们只要将 #define _DEBUGNOW 注释掉即可,无需进行任何额外的操作。怎么样?是不是很方便呢?善用调试标记可以大大地提高我们的调试效率,但是有一点记住,调试标记名不要过于简单,否则可能和程序中的变量/常量产生冲突。

    在我们的调试过程中,我们常常会需要在不触发断点的情况下输出一些数值,这时我们一般会这么做:
    printf("%d\n",Value/*要输出的数值*/);
    但是像这种代码写多了我们可能会对它产生一种厌恶之情,这是我们的预处理器又可以派上用场了,我们可以定义一个宏解决这个问题,定义宏的代码如下:
    #define _PUTINT(NUM) printf("%d\n",NUM)
    然后我们只要这样调用我们的宏:
    _PUTINT(45/*要输出的数值*/);
    怎么样?是不是很方便呢?那如果我们想让这个宏在我们发布程序的时候失效呢,我们该怎么做?其实很简单,我们依然可以使用预处理指令完成这项操作,下面我们来看一套完整的代码(同时使用调试标记和宏):
    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #define _PAUSE() system("pause");
    4. #if (defined DEBUG) || (defined _DEBUG) //检测构建模式是否为调试模式
    5. //如果构建模式为调试模式,这里定义几个宏
    6. #define _DEBUGNOW
    7. #define _PUTSIL(NUM) printf("%d\n",NUM) //输出整数
    8. #define _PUTFD(NUM) printf("%f\n",NUM) //输出浮点数
    9. #else
    10. //如果构建模式为发布模式,自动忽略这些宏的存在
    11. #define _PUTSIL(NUM) ((void)0)
    12. #define _PUTFD(NUM) ((void)0)
    13. #endif
    14. int main(){
    15. #ifdef _DEBUGNOW
    16. printf("正在为调试做准备...\n");
    17. #endif // _DEBUGNOW
    18. printf("程序正在运行...\n");
    19. _PUTSIL(12666);
    20. _PUTFD(3.1415926535898);
    21. printf("程序运行完毕...\n");
    22. _PAUSE(); // 暂停程序
    23. return 0;
    24. }
    我们看到,宏的定义和函数差不多,有的简单的函数可以直接用宏实现,这样做带来的好处就是,我们可以不用频繁的判断是否处于调试状态,一次定义,一直有效。

    为了方便我们的调试(检查)操作以及日后的团队合作,我们在编写函数的时候应该为其加上 Visual Studio 的智能提示,方法比较简单,我们只要在函数定义前面加上提示注释即可(格式自由),Visual Studio 便会自动分析我们的代码并加入其智能提示列表,下面我们举一个定义函数设置智能提示的例子:
    1. //
    2. // 函数: Convert2Jpeg(wchar_t*, wchar_t, int)
    3. //
    4. // 目的: 转换图片到 Jpeg 格式
    5. //
    6. // orgiPath : 源文件路径
    7. // destPath : 目标路径
    8. // quality : 图像质量
    9. //
    10. bool Convert2Jpeg(wchar_t* orgiPath, wchar_t* destPath, int quality){
    11. return true;
    12. }
    我们在自动完成将或者鼠标移动到编辑器内函数名上,就有了智能提示。


    这种智能提示不但可以减轻我们编写代码时的负担,在调试情境下也可以增加我们检查代码的效率。如此一来,岂不美哉!

    接下来我们为大家总结了一些调试的经验以及调试思路:
    当我们运行我们编写的程序发现运行结果与我们预想的不同的时候,我们可以先用即时窗口,使用一些比较简单的数据来测试我们的各个函数运行结果是否符合我们的预期,如果都符合的话,我们可以使用程序中产生的一些比较复杂的数据来进一步测试我们的各个函数,直至找到可能导致错误的函数。

    找到可能导致错误的函数之后,我们就可以使用逐语句调试来一步步跟踪运行程序了,渐渐的的我们就可以缩小范围直至定位错误(无关代码可以考虑暂时注释掉),在这期间,我们要仔细观察程序运行过程中各个数据的变化情况,观察的仔细与否直接与我们能否找到错误直接挂钩。

    如果上一步运行的数据一直是正常的,我们就可以排除这个函数的嫌疑了(减少对他的调试次数)。此时,我们就应该考虑问题是否出现在之前的函数上了,可能因为偶然性,我们第一次测试函数的时候并没有发现其错误,导致范围锁定产生偏差,此时我们需要再次耐心的对所有未排除嫌疑的进行调试,直至再次找到出错的函数。再重复上一步,直至找到错误。

    可以看到,调试其实是一项比较复杂的活,需要大量的操作,所以在我们编写代码的时候要万分谨慎!因为很多时候,BUG都是因为我们的粗心大意导致的笔误引起的!

    展开全文
  • debug调试方法

    万次阅读 2018-08-07 10:20:10
    debug使用方法 2016年12月26日 08:50:...3、运行程序,在后台遇到断点时,进入debug调试状态  =============================  作用域 功能 快捷键  全局 单步返回 F7  全局 单步跳过 F6  全局 单步跳入 F5  ...

    debug使用方法

    2016年12月26日 08:50:25

    阅读数:524

    1.进入debug模式(基础知识列表)
    1、设置断点 
    2、启动servers端的debug模式 
    3、运行程序,在后台遇到断点时,进入debug调试状态 
    ============================= 
    作用域 功能 快捷键 
    全局 单步返回 F7 
    全局 单步跳过 F6 
    全局 单步跳入 F5 
    全局 单步跳入选择 Ctrl+F5 
    全局 调试上次启动 F11 
    全局 继续 F8 
    全局 使用过滤器单步执行 Shift+F5 
    全局 添加/去除断点 Ctrl+Shift+B 
    全局 显示 Ctrl+D 
    全局 运行上次启动 Ctrl+F11 
    全局 运行至行 Ctrl+R 
    全局 执行 Ctrl+U 

    ============================= 
    1.Step Into (also F5) 跳入 
    2.Step Over (also F6) 跳过 
    3.Step Return (also F7) 执行完当前method,然后return跳出此method 
    4.step Filter 逐步过滤 一直执行直到遇到未经过滤的位置或断点(设置Filter:window-preferences-java-Debug-step Filtering) 
    5.resume 重新开始执行debug,一直运行直到遇到breakpoint。 
        例如 :A和B两个断点,debug过程中发现A断点已经无用,去除A断点,运行resume就会跳过A直接到达B断点。 
    6.hit count 设置执行次数 适合程序中的for循环(设置 breakpoint view-右键hit count) 
    7.inspect 检查 运算。执行一个表达式显示执行值 
    8.watch 实时地监视对象、方法或变量的变化 
    9.我们常说的断点(breakpoints)是指line breakpoints,除了line breakpoints,还有其他的断点类型:field(watchpoint)breakpoint,method breakpoint ,exception breakpoint. 
    10.field breakpoint 也叫watchpoint(监视点) 当成员变量被读取或修改时暂挂 
    11.添加method breakpoint 进入/离开此方法时暂挂(Run-method breakpoint) 
    12.添加Exception breakpoint 捕抓到Execption时暂挂(待续...) 
    断点属性: 
       1.hit count 执行多少次数后暂挂 用于循环 
       2.enable condition 遇到符合你输入条件(为ture\改变时)就暂挂 
       3.suspend thread 多线程时暂挂此线程 
       4.suspend VM 暂挂虚拟机 
    13.variables 视图里的变量可以改变变量值,在variables 视图选择变量点击右键--change value.一次来进行快速调试。 
    14.debug 过程中修改了某些code后--〉save&build-->resume-->重新暂挂于断点 
    15.resume 当debug调试跑出异常时,运行resume,重新从断点开始调试 
    16.如果一行有很多方法, 
    当第一次按F5键就跳入这一行第一个方法,F6一步一步调试,F7跳出这方法. 
    当第二次按F5键就跳入这一行第二个方法,F6一步一步调试,F7跳出这方法. 
    以此类推.想进入这一行第几个方法,就按几次F5和F7.

     

    2.基础知识篇(包含部分实践)

    Eclipse 调试器本身是 Eclipse 内的一个标准插件集。Eclipse 还有一个特殊的 Debug 视图,用于在工作台中管理程序的调试或运行。它可以显示每个调试目标中挂起线程的堆栈框架。程序中的每个线程都显示为树中的一个节点,Debug 视图显示了每个运行目标的进程。如果某个线程处于挂起状态,其堆栈框架显示为子元素。

    在使用 Eclipse 调试器之前,假定您已经安装了合适的 Java SDK/JRE(我推荐使用 Java VM V1.4)和 Eclipse Platform SDK V3.3,而且两者的运行都没问题。一般来说,先用 Eclipse 示例测试一下调试选项比较好。如果想开发和调试 C/C++ 项目,还需要获得并安装 C/C++ 开发工具(C/C++ Development Tools,CDT)。关于 Java SDK/JRE、Eclipse 平台和示例以及 CDT,请参阅 参考资源。 图 1 显示了 Debug 透视图的一般视图。


    图 1. Eclipse Debug 透视图的一般视图
    Eclipse Debug 透视图的一般视图
     

    调试 Java 语言程序

    在可以调试项目前,需要干净地编译和运行代码。首先,需要为应用程序创建一个运行配置,确保应用程序可以正确启动。然后,需要通过 Run > Debug 菜单以类似的方式设置调试配置。还需要选择一个类,将它作为调试的主 Java 类来使用(请参见图 2)。您可以按照自己的意愿为单个项目设置多个调试配置。当调试器启动时(从 Run > Debug),会在一个新的窗口中打开,这时就可以开始调试了。


    图 2. 在调试配置中设置项目的主 Java 类
    在调试配置中设置项目的主 Java 类
     

    接下来,将讨论 Eclipse 中的一些常用调试实践。

    设置断点

    在 启动应用程序进行调试时,Eclipse 会自动切换到 Debug 透视图。毫无疑问,最常见的调试步骤是设置断点,这样可以检查条件语句或循环内的变量和值。要在 Java 透视图的 Package Explorer 视图中设置断点,双击选择的源代码文件,在一个编辑器中打开它。遍历代码,将鼠标放在可疑代码一行的标记栏(在编辑器区域的左侧)上,双击即可设置断点。


    图 3. 编辑器左侧看到的两个断点
    编辑器左侧看到的两个断点
     

    现在,从 Run > Debug 菜单启动调试会话。最好不要将多条语句放在一行上,因为会无法单步执行,也不能为同一行上的多条语句设置行断点。


    图 4. 视图中左侧空白处的箭头指示当前正在执行的行
    视图中左侧空白处的箭头指示当前正在执行的行
     

    还有一个方便的断点视图来管理所有的断点。


    图 5. 断点视图
    断点视图
     

    条件断点

    一 旦了解到错误发生的位置,您可能想要知道在程序崩溃之前,程序在做什么。一种方法就是单步执行程序的每行语句。一次执行一行,直到运行到可疑的那行代码。 有时,最好只运行一段代码,在可疑代码处停止运行,在这个位置检查数据。还可以声明条件断点,它在表达式值发生变化时触发(请参见图 6)。除此之外,在输入条件表达式时,也可以使用代码帮助。


    图 6. 设置条件断点触发器
    设置条件断点触发器
     

    计算表达式的值

    为了在 Debug 透视图的编辑器中计算表达式的值,选择设置了断点的那行代码,在上下文菜单中,通过 Ctrl+Shift+I 或右键单击您感兴趣的变量(参见图 7)选择 Inspect 选项。在当前堆栈框架的上下文中会计算表达式的值,在 Display 窗口的 Expressions 视图中会显示结果。


    图 7. 通过 Inspect 选项计算表达式的值
    通过 Inspect 选项计算表达式的值
     

    剪切活动代码

    Display 视图允许您以剪切类型的方式处理活动代码(参见图 8)。要处理一个变量,在 Display 视图中输入变量名即可,视图会提示您一个熟悉的内容助手。


    图 8. Display 视图
    The Display view
     

    当 调试器停止在一个断点处时,您可以从 Debug 视图工具栏(参见图 9)中选择 Step Over 选项,继续调试器会话。该操作会越过高亮显示的那行代码,继续执行同一方法中的下一行代码(或者继续执行调用当前方法的那个方法的下一行代码)。执行上一 步后发生改变的变量会用某种颜色高亮显示(默认是黄色)。颜色可以在调试首选项页面中改变。


    图 9. 改变颜色的变量
    改变颜色的变量
     

    要在 Debug 视图中挂起执行线程,选择一个运行线程,单击 Debug 视图工具栏中的 Suspend。 该线程的当前调用堆栈就会显示出来,当前执行的代码行就会在 Debug 透视图中的编辑器中高亮显示。挂起一个线程时,将鼠标放在 Java 编辑器中的变量上,该变量的值就会在一个小的悬停窗口中显示出来。此时,该线程的顶部堆栈框架也会自动选中,其中的可视变量也会在 Variables 视图中显示出来。您可以通过单击 Variables 视图中合适的变量名来检查变量。

    热交换错误修正:动态代码修正

    如 果运行的是 Java 虚拟机(Java Virtual Machine,JVM)V1.4 或更高的版本,Eclipse 支持一个叫做热交换错误修正(Hotswap Bug Fixing)的功能,JVM V1.3 或更低的版本不支持该功能。该功能允许在调试器会话中改变源代码,这比退出应用程序、更改代码、重新编译、然后启动另一个调试会话更好。要利用该功能,在 编辑器中更改代码后重新调试即可。由于 JVM V1.4 与 Java 平台调试器架构(Java Platform Debugger Architecture,JPDA)兼容,所以才有可能具备该功能。JPDA 实现了在运行的应用程序中替换经过修改的代码的功能。如果应用程序启动时间较长或执行到程序失败的地方时间很长,那么这一点特别有用。

    如果在完成调试时,程序还没有全部执行一遍,在 Debug 视图的上下文菜单中选择 Terminate 选项。容易犯的一个错误是在调试器会话中使用 Debug 或 Run,而不是 Resume。这样做会启动另一个调试器会话,而不是继续当前会话。

     

    3.DEBUG调试方式(方法汇总篇)

    1、 条件断点 
    断点处点击鼠标右键 - 选择"Breakpoint Properties" 
    勾选"Enabled" 启动断点 
    勾选"Hit Count" 输入框写运行次数 
    勾选"Enable Condition" 输入框写停止条件 

    2、 变量断点 
    变量也可以接受断点,在变量的值初始化,或是变量值改变时可以停止 
    可以加条件,和上面条件断点的设置是一样的 

    3、 方法断点 
    方法断点的特别之处在于它可以打在 JDK的源码里 
    由于 JDK 在编译时去掉了调试信息,所以普通断点是不能打到里面的 
    但是方法断点却可以,可以通过这种方法查看方法的调用栈 

    4、 改变变量值 
    在Debug 视图的 Variables 小窗口中, 
    可以在变量上右键,选择"Change Value..." 在弹出的对话框中修改变量的值 
    或是在下面的值查看窗口中修改,Ctr+S 保存 

    5、 重新调试 
    这种调试的回退不是万能的,最多只能退回到当前线程的调用的开始处 
    回退时,请在需要回退的线程方法上点右键,选择 "Drop to Frame" 

    6、 远程调试 
    用于调试不在本机上的程序 
    连接时远程服务器时,需要在Eclipse中新建一个远程调试程序 

    7、异常断点 
    要找到异常发生的地方比较困难,还好可以打一个异常断点 
    Breakpoints工具框顶右第四使用"增加Exception异常断点" 
    当异常发生时,代码会停在异常发生处,定位问题时应该比较有帮助

     

    4.各种断点设置方法(实践篇)

    大家肯定都用过Eclipse的调试的功能,在调试的过程中自然也无法避免要使用断点(breakpoint),但不知是否对Eclipse中各类断点都有所了解。本篇图文并茂地介绍了Eclipse中全部类型的断点,及其设置,希望对大家有所帮助。(2011.11.20)

    1. 示例程序
    BreakpointDemo是一个臆造的应用程序,只是为了便于讲解Eclipse中各类断点的使用罢了。其代码如下图所示,

    BreakpointDemo主要包含两个方法:
    [1]setValue,该方法根据指定的次数(count),对成员变量value进行赋值,值的范围为0-9的随机整数。
    [2]printValue,该方法会调用setValue()对value进行赋值,并打印出value的值;但,如果value能被3整除,那么就会抛出IllegalArgumentException异常。

    2. Line Breakpoint
    Line Breakpoin是最简单的Eclipse断点,只要双击某行代码对应的左侧栏,就对该行设置上断点。此处,对第20行代码设置上Line Breakpoint,如下图所示,
     
    可以为Line Breakpoint设置一个条件,那么当程序运行到该断点时,只有满足设定的条件,才会被中断。右键点击第20行的断点,选择"Breakpoint Properties..."
     
    在弹出的属性对话框中,勾选上"Conditional",然后在文本框中输入"count % 2 == 0"。
     
    该条件表示,当程序运行到第20行时,只有当count为偶数时,程序才会被中断。细心地话,你会发现该断点的图标发生了改变,多了一个问号。
     

    3. Watchpoint
    Line Breakpoint关注于程序运行的"过程",大家也常把使用这种断点的调试称为单步调试。但有时候,我们对程序的运行过程不太了解,可能也不太关心,不能确定在什么地方设置断点比较合适,而可能比较关注某个关键变量的变化或使用。此时,就可以为该变量设置一种特殊的断点--Watchpoint。在此示例,我们最关心的就是成员变量value的值,那么就可以为它设置一个Watchpoint,双击第9行代码对应的左侧栏就可以了。
     
    使用在2中所提及的方法,查看该断点的属性,
     
    默认地,当该变量被访问或它的值被修改时,程序都会被中断。但在本示例中,只希望当对value的值进行修改时程序才需要被中断,所以取消对"Access"的勾选。
     
    这时,我们会发现原来的Watchpoin图标也有变化了。
     

    4. Method Breakpoint
    与关注对某个变量的访问与修改一样,我们也可以关注程序对某个方法的调用情况,即,可以设置Method Breakpoint。在此处,设置针对方法setValue的Method Breakpoint。同理,双击第11行代码对应的左侧栏即可。
     
    仍然要查看该断点的属性。默认地,只勾选了"Entry",而没有勾选"Exit"。

    这表示,当刚进入该方法(调用开始)时,程序会被中断;而,离开该方法(调用结束)时,程序并不会被中断。在本示例中,需要同时勾选上"Exit"。

    点击OK之后,可以看到该断点的图标也有所改变。
     
    根据这里的设置,当程序运行到第20行后会在第12行被中断,尽管这里没有显式的断点,但这就是setValue()方法的入口(Entry)。必须注意地是,程序在运行到第16行时不会被中断,尽管它看起来像是setValue()方法的出口(Exit)。实际上,程序会在第17行被中断,这里才是setValue()调用结束的地方。

    5. Exception Breakpoint
    如果,我们期望某个特定异常发生时程序能够被中断,以方便查看当时程序所处的状态。通过设置ExceptionBreakpoint就能达到这一目标。本示例故意在第23行抛出了IllegalArgumentException异常,我们期望程序运行到此处时会被中断。但我们不直接为此行代码设置Line Breakpoint,而是为IllegalArgumentException设置Exception Breakpoint。设置Exception Breakpoint的方法与其它类型断点都不同,它不能通过双击左侧栏的方式在代码编辑器上直接进行设置。点击Breakpoints视图右上角形如Ji的图标,
     
    会弹出如下所示的对话框,
     
    在其中选中IllegalArgumentException,并点击OK,这时一个Exception Breakpoint就设置好了。

    当value为3的倍数时,程序会在第23行被中断,这时我们就能使用调试器来看看value具体是等于0,3或6。


    6. Class Load Breakpoint
    还有一种大家平时可能不太用的断点--Class Load Breakpoint,即当某个类被加载时,通过该断点可以中断程序。

    转载于:http://langgufu.iteye.com/blog/1168366

    博主:正在走向大牛的菜鸟

    展开全文
  • 软件调试方法及调试原则

    万次阅读 2018-11-16 09:08:37
    调试(Debug)   软件调试是在进行了成功的测试之后才开始的工作,它与软件测试不同,调试的任务是进一步诊断和改正程序中潜在的错误。   注: 以问题为中心 以错误为导向   调试活动由两部分组成: u ...

    调试(Debug)

     

    软件调试是在进行了成功的测试之后才开始的工作,它与软件测试不同,调试的任务是进一步诊断和改正程序中潜在的错误。

     

    注:

    以问题为中心

    以错误为导向

     

    调试活动由两部分组成:

    u  确定程序中可疑错误的确切性质和位置

    u  对程序(设计,编码)进行修改,排除这个错误

     

    注:

    测试和调试密不可分

    测试是为了发现问题

    调试时为了解决问题

     

    调试工作是一个具有很强技巧性的工作

     

    软件运行失效或出现问题,往往只是潜在错误的外部表现,而外部表现与内在原因之间常常没有明显的联系,如果要找出真正的原因,排除潜在的错误,不是一件易事。

    可以说,调试是通过现象,找出原因的一个思维分析的过程。

    调试步骤:

    (1)      从错误的外部表现形式入手,确定程序中出错位置

    (2)      研究有关部分的程序,找出错误的内在原因

    (3)      修改设计代码,以排除这个错误

    (4)      重复进行暴露了这个错误的原始测试或某些有关测试。

     

    注:

    由外而内

    由现象到本质

    由结果到原因

     

    从技术角度来看查找错误的难度在于:

    u  现象与原因所处的位置可能相距甚远

    u  当其他错误得到纠正时,这一错误所表现出的现象可能会暂时消失,但并为实际排除

    u  现象实际上是由一些非错误原因(例如,舍入不精确)引起的

    u  现象可能是由于一些不容易发现的人为错误引起的

    u  错误是由于时序问题引起的,与处理过程无关

    u  现象是由于难于精确再现的输入状态(例如,实时应用中输入顺序不确定)引起

    u  现象可能是周期出现的,在软,硬件结合的嵌入式系统中常常遇到

     

     

    几种主要的调试方法

     

    调试的关键在于推断程序内部的错误位置及原因,可以采用以下方法:

    1.强行排错:

    这种调试方法目前使用较多,效率较低,它不需要过多的思考,比较省脑筋。例如:

    u  通过内存全部打印来调试,在这大量的数据中寻找出错的位置。

    u  在程序特定位置设置打印语句,把打印语句插在出错的源程序的各个关键变量改变部位,重要分支部位,子程序调用部位,跟踪程序的执行,监视重要变量的变化

    u  自动调用工具,利用某些程序语言的调试功能或专门的交互式调试工具,分析程序的动态过程,而不必修改程序。

     

    应用以上任一种方法之前,都应当对错误的征兆进行全面彻底的分析,得出对出错位置及错误性质的推测,再使用一种适当的调试方法来检验推测的正确性。

     

    2.回溯法调试:

    这是在小程序中常用的一种有效的调试方法,一旦发现了错误,人们先分析错误的征兆,确定最先发现“症状“的位置

    然后,人工沿程序的控制流程,向回追踪源程序代码,直到找到错误根源或确定错误产生的范围,

    例如,程序中发现错误处是某个打印语句,通过输出值可推断程序在这一点上变量的值,再从这一点出发,回溯程序的执行过程,反复思考:“如果程序在这一点上的状态(变量的值)是这样,那么程序在上一点的状态一定是这样···“直到找到错误所在。

     

     

     

    3.归纳法调试:

    归纳法是一种从特殊推断一般的系统化思考方法,归纳法调试的基本思想是:从一些线索(错误征兆)着手,通过分析它们之间的关系来找出错误

     

    u  收集有关的数据,列出所有已知的测试用例和程序执行结果,看哪些输入数据的运行结果是正确的,哪些输入数据的运行经过是有错误的

    u  组织数据

    由于归纳法是从特殊到一般的推断过程,所以需要组织整理数据,以发现规律

     

    常以3W1H形式组织可用的数据

    “What“列出一般现象

    “Where“说明发现现象的地点

    “When“列出现象发生时所有已知情况

    “How“说明现象的范围和量级

    “Yes“描述出现错误的3W1H;

    “No“作为比较,描述了没有错误的3W1H,通过分析找出矛盾来

     

    u  提出假设

    分析线索之间的关系,利用在线索结构中观察到的矛盾现象,设计一个或多个关于出错原因的假设,如果一个假设也提不出来,归纳过程就需要收集更多的数据,此时,应当再设计与执行一些测试用例,以获得更多的数据。

     

    u  证明假设

    把假设与原始线索或数据进行比较,若它能完全解释一切现象,则假设得到证明,否则,认为假设不合理,或不完全,或是存在多个错误,以致只能消除部分错误

     

     

    4.演绎法调试:

    演绎法是一种从一般原理或前提出发,经过排除和精华的过程来推导出结论的思考方法,演绎法排错是测试人员首先根据已有的测试用例,设想及枚举出所有可能出错的原因作为假设,然后再用原始测试数据或新的测试,从中逐个排除不可能正确的假设,最后,再用测试数据验证余下的假设确是出错的原因。

     

    u  列举所有可能出错原因的假设,把所有可能的错误原因列成表,通过它们,可以组织,分析现有数据

    u  利用已有的测试数据,排除不正确的假设

    仔细分析已有的数据,寻找矛盾,力求排除前一步列出所有原因,如果所有原因都被排除了,则需要补充一些数据(测试用例),以建立新的假设。

     

    u  改进余下的假设

    利用已知的线索,进一步改进余下的假设,使之更具体化,以便可以精确地确定出错位置

     

    u  证明余下的假设

     

     

    调试原则

    n  在调试方面,许多原则本质上是心理学方面的问题,调试由两部分组成,调试原则也分成两组。

    n  确定错误的性质和位置的原则

    u  用头脑去分析思考与错误征兆有关的信息

    u  避开死胡同

    u  只把调试工具当做辅助手段来使用,利用调试工具,可以帮助思考,但不能代替思考

    u  避免用试探法,最多只能把它当做最后手段

     

    n  修改错误的原则

     

    u  在出现错误的地方,很有可能还有别的错误

    u  修改错误的一个常见失误是只修改了这个错误的征兆或这个错误的表现,而没有修改错误的本身。

    u  当心修正一个错误的同时有可能会引入新的错误

    u  修改错误的过程将迫使人们暂时回到程序设计阶段

    u  修改源代码程序,不要改变目标代码
     

    展开全文
  • devc++调试方法

    万次阅读 多人点赞 2020-08-20 11:19:22
    简述:对代码的调试主要目的在于,通过让程序单步执行,使读者详细的看见每一步的代码执行过程和结果,方便找到错误信息的所在! 1开启调试模式 1.1 点开工具 1.2选择编译器选项 1.3 更改产生调试信息为yes 2...
  • 手机web前端调试页面的几种方式

    千次阅读 2019-07-18 00:21:15
    PC端web页面调试比较容易,这里主要说几种移动端调试的方法,从简单到复杂、从模拟调试到远程调试,大概分为几部分: 1、Chrome DevTools(谷歌浏览器)的模拟手机调试 2、weinre(web inspectorremote)远程调试...
  • VS下如何调试程序

    万次阅读 多人点赞 2018-02-25 15:20:58
    程序崩溃的原因分类函数栈溢出 一个变量未初化、未赋值,就读取它的值。 ( 这属于逻辑问题,往往是粗心大意的导致的 )函数栈溢出 (1)定义了一个体积太大的局部变量 (2)函数嵌套调用,层次过深(如无穷递归...
  • IDEA调试技巧

    千次阅读 多人点赞 2019-05-23 17:55:39
    调试代码的时候,你的项目得debug模式启动,也就是点那个绿色的甲虫启动服务器,然后,就可以在代码里面断点调试啦。 下面不要在意,这个快捷键具体是啥,因为,这个keymap是可以自己配置的,有的人keymap是mac版...
  • web项目调试

    千次阅读 2018-08-15 15:43:33
    web项目往往由前端和后台组成,涉及的内容和知识特别多,初学者由于对其运行机制理解不到位,同时没有掌握恰当的调试方法,当项目出现问题时就比较茫然、无从下手。提高调试能力对于程序员来讲是至关重要,是成为一...
  • 1.为什么要进行程序调试调试程序的目的? 程序调试是将编制的程序投入实际运行前,用手工或 编译程序等方法进行测试,修正语法错误和逻辑错误的过程。这是保证计算机信息系统正确性的必不可少的步骤。 测试时程序...
  • 几个主要软件调试方法及调试原则

    万次阅读 2014-07-31 17:40:12
    调试(Debug)   软件调试是在进行了成功的测试之后才开始的工作,它与软件测试不同,调试的任务是进一步诊断和改正程序中潜在的错误。   调试活动由两部分组成: u 确定程序中可疑错误的确切性质和位置 u...
  • 在下载完DEV-C++以后进行第一次调试时,系统弹出以上窗口,点击Yes按钮后编译器出现秒退的情况。具体解决方案为:菜单栏&gt;&gt;...产生调试信息&gt;&gt;更改为Yes;附一张图:...
  • 一个完整的gdb调试过程以及一些常用的命令

    万次阅读 多人点赞 2020-04-24 18:46:03
    1. 启动调试 gcc -g test.c // 编译文件,生成可执行程序,并且给文件加上调试信息 gdb a.out // 开始启动调试 2. 显示当前的代码:l 另外: l 函数名 // 显示这个函数的代码(10行) 如果想继续查看,继续...
  • Dev C++调试方法

    万次阅读 多人点赞 2018-09-05 14:46:55
    来源https://blog.csdn.net/hz18790581821/article/details/78418648 基本信息 &amp;nbsp; &amp;nbsp; &amp;nbsp;...简述:对代码的调试主要目的在于,通过让程序单步执行,使读...
  • DEV C++ "把着手教" 单步调试(debug)

    万次阅读 多人点赞 2016-04-04 11:46:20
    devC++ 单步调试 这个在较小的代码里相对有用,但是算法题和工程调试,还是建议输出调试 –很多人都这么说.Step1 新建一个工程(可选) 可能要调dev为支持debug模式 Step2 敲入(粘贴..)
  • Android蓝牙调试助手

    万次阅读 热门讨论 2019-09-20 11:20:48
    PS:最近一直在搞Android上的蓝牙应用,每次在PC上调试蓝牙设备中的数据还可以,但是在Android手机上就非常不方便,所以自己写了个简单的Android蓝牙调试助手,希望各位下载使用,如有问题和Bug跟贴。 源码下载...
  • "无法找到“XXX.exe”的调试信息,或者调试信息不匹配。未使用调试信息生成二进制文件"Debug模式,运行时完全正常,但是一调试就出现对话框,显示出错信息: “无法找到“XXX.exe”的调试信息,或者调试信息不匹配。...
  • VS2015 远程调试方法

    万次阅读 2017-08-25 16:08:02
    因此采用远程调试。下面介绍启动远程调试的方法。 第一步:拷贝C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Remote Debugger文件夹下x86到虚拟机中。在虚拟机中启动msvsmon.exe,如下图: ...
  • 手机无法弹出允许usb调试解决方法

    万次阅读 2016-04-14 00:06:06
    手机无法弹出允许usb调试解决方法 注意: 此文针对以前连接电脑能正常调试,因某些原因导致不能使用调试模式的问题 第一次连接电脑出问题的请另寻搜索 正常情况下,手机打开usb调试模式连接电脑会弹出允许usb调试对话框...
  • 使用PC上的 Chrome 远程调试手机端的页面 工具准备 手机端:chrome for Android,;  PC端:安装谷歌浏览器(最好是最新版的开发者版本) USB 连接线, 也就是你充电器的那条线 开启调试模式 使用 ...
1 2 3 4 5 ... 20
收藏数 1,535,016
精华内容 614,006
关键字:

调试