## 设置断点调试 默认情况下,程序不会进入调试模式,代码会瞬间从开头执行到末尾。要想观察程序的内部细节,就得让程序在某个地方停下来,我们可以在这个地方设置断点。 所谓断点(BreakPoint),可以理解为障碍物,人遇到障碍物不能行走,程序遇到断点就暂停执行。 插入断点有多种方式: - 点击代码行左侧; - F9 - 右键选择 插入断点后,点击上方的“运行”按钮,或者按`F5`键,即可进入调试模式。 可以看到,程序虽然运行了,但并未输出任何数据,这是因为在输出数据之前就暂停了。同时,在IDE的上方出现了与调试相关的工具条,下方也多出了几个与调试相关的窗口: - `调用堆栈`可以看到当前函数的调用关系。 - `断点`窗口可以看到当前设置的所有断点。 - `即时窗口`可以让我们临时运行一段代码,后续我们会重点讲解。 - `输出窗口`和我们之前看到的一样,用来显示程序的运行过程,给出错误信息和警告信息。 - `自动窗口`会显示当前代码行和上一代码行中所使用到的变量。 - `局部变量`窗口会显示当前函数中的所有局部变量。 - `线程`和`模块`窗口暂时无需理会。 如果你的VS缺少某个窗口,可以通过VS上方的“调试”菜单调出,如下图所示: **断点的真正含义** 严格来说,调试器遇到断点时会把程序暂时挂起,让程序进入一种特殊的状态——中断状态,这种状态下操作系统不会终止程序的执行,也不会清除与程序相关的元素,比如变量、函数等,它们在内存中的位置不会发生变化。 关键是,处于中断状态下的程序允许用户查看和修改它的运行状态,比如查看和修改变量的值、查看和修改内存中的数据、查看函数调用关系等,这就是调试的奥秘。 **继续执行程序** 点击“运行”按钮或者按`F5`键即可跳过断点,让程序恢复正常状态,继续执行后面的代码,直到程序结束或者遇到下一个断点。在调试过程中,按照上面的方法可以设置多个断点,程序在执行过程中每次遇到断点都会暂停。 **删除断点** 如果不希望程序暂停,可以删除断点。删除断点也很简单,在原有断点处再次单击鼠标即可,也可以将光标定位到要删除断点的代码行,再次按`F9`键,或者在右键菜单中删除。 **手动暂停** 在VS下,程序运行结束后不会自动暂停(一闪而退),要手动添加暂停语句`system("pause");`,如果大家觉得麻烦,也可以在代码最后插入断点,强制程序暂停。 ## 查看和修改变量的值 设置了断点,就可以观察程序的运行情况了,其中很重要的一点就是查看相关变量的值,这足以发现大部分逻辑错误。 将下面的代码复制到源文件中: ```c #include int main() { int value_int, array_int[3]; float value_float; char* value_char_pointer; //在这里插入断点 value_int = 1048576; value_float = 2.0; value_char_pointer = "Hello World"; array_int[0] = 379; array_int[1] = 94; //在这里插入断点 return 0; } ``` 运行到第一个断点时,在`局部变量`窗口可以看到各个变量的值。可以看到,未经初始化的局部变量和数组的值都是垃圾值,是随机的,没有意义。双击变量的值,可以进行修改。 点击“运行”按钮或按`F5`键,程序会运行到下一个断点位置,在`局部变量`窗口可以看到各个值的变化。 **更加快捷的方式** 除了在窗口中查看变量,还有一种更加便捷的方法:在调试模式下,把鼠标移动到要查看的变量的上方,即可看他它的值。 如果是数组、指针、结构体等还可以展开。这种查看变量的方式在实际开发中使用很多。 **添加监视** 如果你希望长时间观测某个变量,还可以将该变量添加到监视窗口。在要监视的变量处单击鼠标右键选择“添加监视”,在VS下方的`监视`窗口就可以看到当前变量。这样,每次变量的值被改变都会反映到该窗口中,无需再将鼠标移动到变量上方查看其值。尤其是当程序稍大时,往往需要同时观测多个变量的值,添加监视的方式就会显得非常方便。 ## 单步调试(逐语句调试和逐过程调试) 在实际开发中,常常会出现这样的情况,我们可以大致把出现问题的代码锁定在一定范围内,但无法确定到底是哪条语句出现了问题,该怎么办呢?按照前面的思路,必须要在所有代码行前面设置断点,让代码一个断点一个断点地执行。 这种方案确实可行,但很麻烦,也不专业,这节我们就来介绍一种更加便捷的调试技巧——单步调试。所谓单步调试,就是让代码一步一步地执行。 **逐过程调试(F10)和逐语句调试(F11)** 刚才我们在第6行设置了断点,按下“逐过程”按钮或`F10`键,程序就会执行 printf(),并暂停在下一行代码处。 printf() 是一个函数,它本身由多条语句构成,如果你希望进入 printf() 函数内部,查看整个函数的执行过程,可以点击“逐语句”按钮,或者按`F11`键,这样程序就会进入 printf() 所在的源文件。 当需要跳出函数时,可以点击“跳出”按钮,或者按`Shift+F11`键,就会返回刚才的代码。 `逐过程(F10)`和`逐语句(F11)`都可以用来进行单步调试,但是它们有所区别: - `逐过程(F10)`在遇到函数时,会把函数从整体上看做一条语句,不会进入函数内部; - `逐语句(F11)`在遇到函数时,认为函数由多条语句构成,会进入函数内部。 `逐语句(F10)`不仅可以进入库函数的内部,还可以进入自定义函数内部。在实际的调试过程中,两者结合可以发挥更大的威力。 断点 + 查看/修改变量 + 逐过程调试 + 逐语句调试,这足以解决绝大多数逻辑问题,到此,初学者就已经学到了调试的基本技能。 **修改代码运行位置** 在VS中,调试器还允许我们直接跳过一段代码,不去执行它们。 在第3行设置断点,开始单步调试。假设我们不希望执行4~6行的代码,那么当程序执行到第4行时,可以将鼠标移动到黄色箭头处,直接向下拖动到第7行,如下图所示: ```c #include int main(){ printf("111\n"); printf("222\n"); printf("333\n"); printf("444\n"); printf("555\n"); printf("666\n"); return 0; } ``` 注意:随意修改程序运行位置是非常危险的行为,假设我们定义了一个指针,在第N行代码中让它指向了一个数组,如果我们在修改程序运行位置的时候跳过了第N行代码,并且后面也使用到了该指针,那么就极有可能导致程序崩溃。 ## 即时窗口的使用 “即时窗口”是VS提供的一项非常强大的功能,在调试模式下,我们可以在即时窗口中输入C语言代码并立即运行。 在即时窗口中可以使用代码中的变量,可以输出变量或表达式的值(无需使用printf()函数),也可以修改变量的值。 即时窗口本质上是一个命令解释器,它负责解释我们输入的代码,再由VS中的对应模块执行,最后将输出结果呈现到即时窗口。 需要注意的是,在即时窗口中不能定义新的变量,因为程序运行时 Windows 已经为它分配好了只够刚好使用的内存,定义变量是需要额外分配内存的,所以调试器不允许在程序运行的过程中定义变量,因为这可能会导致不可预知的后果。 **调用函数** 在即时窗口中除了可以使用代码中的变量,也可以调用代码中的函数。将下面的代码复制到源文件中: ```c int plus(int x, int y){ return x + y; } int main(){ return 0; } ``` 在第6行设置断点,并在即时窗口中输入`plus(5, 6)`。