【维生素C语言】第八章 - 实用调试技巧(上)

233 阅读8分钟

​​​​「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

前言:

一名优秀的程序员是一名出色的侦探,每一次调试都是尝试破案的过程……本章将详细带你学习实用调试技巧!正式开启DEBUG生活。

一、调试(Debug)

0x00 何为调试

一名优秀的程序员是一名出色的侦探,每一次调试都是尝试破案的过程……

📚 定义:调试,又称除错,是发现和减少计算机程序电子仪器设备中程序错误的一个过程;

0x01 调试的基本步骤

📚 基本步骤:

① 发现程序错误的存在;

✅ 能够发现错误的人:

⑴ 程序员,自己发现;

⑵ 软件测试人员,测试软件;

⑶ 用户,代价严重;

📜 箴言:要善于承认自己的错误,不能掩盖错误;

② 以隔离、消除等方式对错误进行定位;

✅ 能知道大概在什么位置,再确定错误产生的原因是什么;

③ 提出纠正错误的解决方案;

④ 对程序错误订正,重新调试;

二、Debug和Release的介绍

0x00 对比

📚 Debug 通常称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序;

📚 Release 称为发布版本,他往往是进行了各种优化,使得程序在代码大小和运行速度上是最优的,以便用户更好的使用;

📌 注意事项:Release 版本是不能调试的;

💬 用 Debug 和 Release 分别运行:

int main()
{
    char* p = "hello,world!";
    printf("%s\n", p);

    return 0;
}

🚩 Debug 环境下运行结果如下:

🚩 Release 环境下运行结果如下:

💡 我们可以发现:Release进行了优化,使得程序在运行速度和代码大小上是最优的;

💬 Debug和Release反汇编展示对比:

0x01 Release的优化

❓ 使用Release版本调试时,编辑器进行了那些优化呢?

💬 请看下列代码:

int main()
{
    int arr[10] = {0};
    int i = 0;
    for(i=0; i<=12; i++) {
        arr[i] = 0;
        printf("hehe\n");
    }

    return 0;
}

🚩 如果是 debug 模式去编译,程序结果是 死循环:

🚩 如果是 release 模式去编译,程序没有死循环:

💡 因为 release 的优化,避免了死循环的发生;

三、Windows环境调试介绍

0x00 调试环境准备

📚 在环境中选择 debug 选项,才能使代码正常调试;

📌 注意事项:本章使用 VS2019 演示;

0x01 开始调试(F5)

✅ 快捷键:F5

📚 作用:启动调试,经常用来直接调到下一个断点处;

📌 注意事项:

① 如果直接按 F5 ,如果没有阻挡的话程序一口气就干完了;

② 使用 F5 之前要先使用 F9 ,设置断点;

💬 按 F5 开始调试下列代码:

int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	int i = 0;
	for (i = 0; i < sz; i++) {
		arr[i] = i + 1;
	}
	for (i = 0; i < sz; i++) {
		printf("%d\n", arr[i]);
	}

	return 0;
}

🚩 运行后结果如下:

0x02 断点(F9)

✅ 快捷键:F9

📚 作用:创建断点和取消断点,断电的重要作用可以在程序的任意位置设置断点;这样就可以使得程序在想要的位置随意停止执行,继而可以一步步执行下去;

💬 按 F9 设置断点

🚩 这时按下 F5 就会直接跳到断点部分:

0x03 逐过程(F10)

✅ 快捷键:F10

📚 作用:通常用来处理一个过程,一个过程可以是一次函数的调用,或者是一条语句;

💬 逐过程:

💡 按一次 F10 代码就往下走一步;

0x04 逐语句(F11)

✅ 快捷键:F11(这是最常用的)

📚 作用:每次都执行一条语句,观察的细腻度比 F10 还要高,可以进入到函数内部;

📌 注意事项:F10F11 大部分情况是一样的,区别在于 F11 遇到函数时可以进到函数内部去,函数的内部也可以一步步观察,而 F10 遇到函数调用完之后就跳出去了;

💬 观察函数内部:

💡 如果想观察函数内部,就要使用 F11 (逐语句);

0x05 开始执行不调试(Ctrl + F5)

✅ 快捷键: Ctrl + F5

📚 作用:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用;

0x06 总结

F5 - 启动调试

F9 - 设置/取消断点

F10 - 逐过程

F11 - 逐语句 - 更加细腻

Ctrl + F5 - 运行

📌 注意事项:如果你按上面的快捷键不起作用时,可能是因为辅助功能键(Fn)导致的,此时按下 Fn 再按上面的快捷键即可;

❓ 想知道更多快捷键?

VS中常用的快捷键 👈 戳我!

0x07 调试时查看程序当前信息

💬 查看方法:调试(D) → 窗口(W) → 选择相应的选项;

📌 注意事项:只有调试之后才会显示调试窗口里的选项;

0x08 查看断点

📚 作用:调试多个文件时,可以很好地管理多个文件的断点;

0x09 监视

📚 作用:在调试开始之后,便于观察变量的值;

📌 注意事项:要填入合法的表达式;

💬 监视操作(手动添加):

0x0A 自动窗口

📚 作用:编辑器自行监视,随着代码自动给出值;

📌 注意事项:

① 自动窗口和监视是一样的效果,但是自动窗口里的表达式会自动发生变化;

② 自由度低,自动窗口是编辑器自己监视,你管不了;

0x0B 查看局部变量

📚 作用:查看程序进行到当前位置时上下文的局部变量,编辑器自主放到窗口中进行相应的解释,只有局部变量和数组;

💬 查看局部变量:

0x0C 查看内存信息

📚 作用:在调试开始之后,用于观察内存信息;

💬 查看内存信息:

0x0D 查看调用堆栈

📚 作用:通过调用堆栈,可以清晰地反应函数的调用关系和所处的位置;

💬 查看调用堆栈:

0x0E 查看汇编信息

📚 在调试开始后,有两种方式转到汇编:

① 第一种方式:右击鼠标,选择 " 转到反汇编 "

② 第二种方式:调试 → 窗口 → 反汇编

💬 查看反汇编:

0x0F 查看寄存器信息

📚 作用:可以查看当前运行环境的寄存器的实用信息;

💬 查看寄存器:

0x10 条件断点

❓ 假设某个循环要循环1000次,我怀疑第500次循环时程序会出问题,那么我要打上断点然后再按500次 F10 吗?这样一来手指头不得按断了?

💡 方法:使用条件断点;

💬 在断点设置好之后右键鼠标,选中条件:

🐞 按下 F5 后,i 会直接变为 5 :

0x11 调试的感悟

📜 箴言:

① 多多动手,尝试调试,才能有进步;

② 一定要熟练掌握调试的技巧;

③ 初学者可能80%的时间在写代码,20%的时间在调试。

但是一个程序员可能20%的时间在写程序,但是80%的时间在调试;

④ 我们所讲的都是一些简单的调试。

以后可能会出现很复杂的调试场景:多线程程序的调试等;

⑤ 多多使用快捷键,提升效率;