Learning C++ 之1.11 debugging程序(步进和断点)

xiaoxiao2021-02-28  18

语法和语义错误:

编程非常困难,有很多可能犯错的地方。错误经常分为几类:语法错误和语义错误(逻辑错误)。

语法错误的发生往往是你没有按照C++的标准规范来写代码。这包括类似于缺少分号,未定义的变量,不匹配的括号,比如下面的例子:

#include <iostream>; // preprocessor statements can't have a semicolon on the end int main() { std:cout < "Hi there; << x; // invalid operator (:), unterminated string (missing "), and undeclared variable return 0 // missing semicolon at end of statement }

幸运的是编译器可以准确的告诉你这些错误,你可以很简单的分辨并解决这些问题。你需要重新编译一遍,直到所有的错误修复。

有些时候程序会崩溃,如下面的例子:

#include <iostream> int main() { int a = 10; int b = 0; std::cout << a << " / " << b << " = " << a / b; // division by 0 is undefined return 0; }

有些时候只会生成错误的值:

#include <iostream> int main() { std::cout << "Hello, word!"; // spelling error return 0; }

或者:

#include <iostream> int add(int x, int y) { return x - y; // function is supposed to add, but it doesn't } int main() { std::cout << add(5, 3); // should produce 8, but produces 2 return 0; }

不幸的是编译器不能告诉你这一类的错误,因为编译器只能直到你写了什么,但是并不知道你的意图。

在上面的例子里,错误非常容易发现。但是在大多数重要的程序中,一些错误是没有办法通过眼睛来发现的。

所以这一类的错误就需要使用debugger来发现。

the debugger

debugger是一种程序,允许编程人员来控制并且试试显示程序运行状态的程序。

比如程序员可以使用debugger逐行地跟踪程序定位,检查函数的值。通过代码的实际值和期望值做对比,或者观察程序的实际执行路径,debugger可以最大的发现程序的语义错误。

早期的debugger程序,如GDB,是通过命令行控制的,程序员需要敲命令来让它工作。最近的调试器,如Borland的turbo调试器都是图像化操作的了。几乎所有的IDE都有内置的debugger程序,多以你就可以直接在你的编译环境中来调试程序。

现在所有的IDE的调试器都有相同的调试环境以及方法,但是具体的菜单访问方式以及快捷键的设置基本上没有什么一致性。尽管我们下面的例子使用的是Visual Studio 2005,当你使用其他的IDE的时候应该会重新熟悉你的debugger环境。

在开始之前,请确保你的code是debug环境。

步进:

步进的方式是可以让你逐行地跟踪代码,这可以让你跟踪每一行代码地执行,以观察代码是否按照你的要求工作。

实际上只有三个不同地命令:step into,step over,step out,我们会一步一步地学习。

step into:

step into就是执行下一行的代码。如果这一行是一个函数调用,step into会直接跳进相应的函数的第一行。

让我们看一个非常简单的程序:

#include <iostream> void printValue(int nValue) { std::cout << nValue; } int main() { printValue(5); return 0; }

你应该知道,我们的程序是从main函数开始执行的。因为我们想要在main函数里面调试,所以在main里面使用step into。在Visual Stadio里面你可以在调试栏里面找到逐语句,然后调试,或者直接使用F11.

使用其他的IDE你也可以找到类似的调试->逐语句,然后调试。

当你选择完成之后会有两件事情发生。首先因为我们的程序是一个控制台程序,所以会跳出一个控制台。这个控制台应该是空的,因为我们没有输出任何东西。其次你应该看到一个标记性的东西,如箭头之类的。

这箭头表示该行程序将在下一行执行。在这个例子里,表示下一步执行的程序是main函数的{,再次选择逐步调试,或者F11,箭头就会走到下一行,执行PrintValue函数。

这意味着函数将会走到下一行的函数中,就会走到PrintValue中的{。

继续执行step into就会走到std::cout里面,这个时候选择step over,这将执行这个语句,并且不用执行<<操作符。这个时候你就能看到5已经输出在控制台了。

这次选择step over,你会发现下一步不会调到std::cout里面。由于cout已经执行,你就看到了5输出在屏幕上了。

再次选择step into,你就会发现箭头已经直到main函数里了。

这个意思是调式已经调用到PrintValue的返回了 。

step over:

就像step into,step over也是逐步执行的。如果碰到一个函数,step over会直接跳过这个函数,直接把函数的执行结果返回给你。

让我们用相同的程序看一下这个例子:

#include <iostream> void printValue(int nValue) { std::cout << nValue; } int main() { printValue(5); return 0; }

step into函数调用的地方,然后直接step over,就会发现直接跳过该函数了,函数已经执行完成。

step over已经提供了一种简单的直接滤过函数的功能,调试更加方便。

step out:

不像step into和step over,step out直接略过剩下的所有code,并把调试权交给你。

具体看下面相同的例子:

#include <iostream> void printValue(int nValue) { std::cout << nValue; } int main() { printValue(5); return 0; }

step into函数里面,直到进入printValue函数:

这个时候直接step out,你会发现直接跳过剩余代码,执行到最后。

执行到光标处:

虽然步进调试可以确认你遇到的所有问题,但是在一个很大的程序中使用这种方法非常费时间。

幸运的是,现在的debugger工具可以提供更好的方法来定位问题。

比较有效的一个方法是执行到光标处,这个命令可以让代码执行到光标所在处,并且调试完成,下面用同一个例子看一下:

#include <iostream> void printValue(int nValue) { std::cout << nValue; } int main() { printValue(5); return 0; }

把光标放到std::cout...这里,然后右键点击,选择执行到光标处。

你会发现代码直接执行到了你的光标所在处。

执行:

一旦你在执行debugger的程序中,你可以直接告诉程序执行到最后或者程序的断点处。在vs中这个命令叫做continue,其他的地方叫做go或者run。如果你还是用上面的例子,你step into print Value的函数的时候,你可以执行run环境,这样程序就执行到最后了。

断点:

最后我们来探讨一下断点,断点是一个特殊的标记,告诉编译器执行的过程中停止编译。设置断点你可以在dubug中选择设置断点,也可以直接双击侧边栏,效果如下:

首先通过step into来进入调试模式,然后点击执行。你会看到程序执行到断点处就停止了。

当你需要验证一段特殊的代码的时候,断点是非常有用的。简单的设置一下断点,程序每次运行到这个地方都会停止,这样方便定位问题。

最后的建议:直到现在我们都是通过step into来开始进行调试的。然而有必要告诉编译器直接运行到最后,在VS中,可以直接选择开始调试。这样就可以有效地减少你的输入命令。

结论:

恭喜你已经了解了所有的调试你写的代码的方法了。然而这只是让debugger好好工作的一半内容。下一章将会介绍怎么检查编译的值,以及怎么使用额外的两个窗口调试程序。

转载请注明原文地址: https://www.6miu.com/read-1899976.html

最新回复(0)