👉什么是bug?👈
Bug作为一个英文单词,实际上它的中文含义并没有“漏洞”这个意思,原意是指小虫子、传染病、着迷以及窃听等等,但是在1947年9月9日之后,“Bug”就变成了错误或者漏洞的代称,据说当时一位计算机专家赫柏正在对17000个继电器进行程序的编辑,奈何突然发现整机停止运作。
等到工作人员查看了巨大的计算机整体后发现,原来是其中的一组继电器上的触点被一只飞蛾所妨碍了,当时因为这个触点的电压非常高,而飞蛾正好受到光和热的吸引撞在了上面,于是就被电死在触点上,于是赫柏就拿出一个纸条写上了“bug”,也就是虫子的意思,以此来代表“一个电脑程序中的错误”,最终“Bug”的说法也就流传了下来。
世界上的第一个BUG:
那么我们怎么才能避免写出BUG呢?如果我们想要不写出BUG,就要提升我们调试代码的能力了。
👉调试是什么?有多重要?👈
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了;如果问心有愧,就必然需要掩盖,那就一定会有迹象。迹象越多就越容易顺藤而上,这就是推理的途径。
顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
而一名优秀的程序员就是一名出色的侦探,拥有强大的调试能力,能够快速地找出BUG。
每一次调试都是尝试破案的过程。
相信大家未来都是一名优秀的程序员!但是似乎现在,也有些人像下图那样子去写代码(BUG)。希望大家不要这样子,一定要写出优秀的代码。
而且排查问题也不应该下图那样,一顿乱弄,然后自己都不知道错在哪里和对在哪里。所以我们一定要学会调试代码。
调试是什么?
调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。
调试的基本步骤
- 发现程序错误的存在
- 以隔离、消除等方式对错误进行定位
- 确定错误产生的原因
- 提出纠正错误的解决办法
- 对程序错误予以改正,重新测试
Debug和Release的介绍
Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
代码示例:
#include <stdio.h> int main() { char* p = "hello bit."; printf("%s\n", p); return 0; }
上述代码在Debug环境的结果展示:
上述代码在Release环境的结果展示:
Debug和Release反汇编展示对比:
通过上面的对比,我们就可以发现Debug版本和Release版本有着明显的区别,这是因为Debug版本中含有调试信息,而且Release版本是经过优化的。
那么编译器进行了那些优化呢?我们通过下面的代码来了解一下。
#include <stdio.h> int main() { int i = 0; int arr[10] = { 0 }; for (i = 0; i <= 12; i++) { arr[i] = 0; printf("hehe\n"); } return 0; }
如果是debug模式去编译,程序的结果是死循环。而如果release模式去编译,程序没有死循环。这就是优化所带来的的明显差异。
👉Windows环境调试介绍👈
调试环境的准备
在环境中选择Debug选项,才能使代码正常调试。如果选择Release选项,是无法进行调试的。
学会快捷键
最常使用的几个快捷键:
F5
启动调试,经常用来直接跳到下一个断点处。
F9
创建断点和取消断点。可以在程序的任意位置设置断点,
使得程序在想要的位置随意停止执行,继而一步步执行下去。
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)。
CTRL + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
注意:如果你发现你按下F9、F10和F11键,都没有出现效果的话,试着同时按下Fn+F9/F10/F10就可以了。
F5的使用
F5一般是配合着F5使用的,F9设置一个断点,然后再按下F5跳到断点处。如果直接按下F5,代码直接运行结束了。举个例子:如果我想直接让箭头执行157行,那么我可以摁一下F9将断点设置在157行,再摁一下F5就能跳到157行了。
F11的使用
F11也是逐语句执行,和F10的功能一样,当时F11可以让执行逻辑进入到函数内部。举个例子:进入调试后,当箭头执行157行时,再摁下F11就可以进入函数内部了。
调试的时候查看程序当前信息
1.查看临时变量的值
在调试开始之后,监视可用于观察临时变量的值。
监视是调试是最最最常用的功能,也是最好用的功能。无论你想查看变量的值,还是变量的地址,都可以使用监视来查看。只要输入变量的名称,就可以查看变量的相关信息了。
2.查看内存信息
在调试开始之后,内存用于观察内存信息。
跳出内存窗口之后,可以调整显示的列数。因为整型变量是4个字节,所以我们将其调整为显示4列,可以更好的观察整型的数据。如下图所示:
我们在内存窗口上输入&a,就能找到a的地址,也可以看到a的数据。如果想要观察其他变量,也可以重新输入。如下图所示:
查看调用堆栈
在调试开始之后,用于查看调用堆栈。
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。
4.查看汇编信息
在调试开始之后,有两种方式转到汇编代码。
(1)第一种方式:右击鼠标,选择【转到反汇编】:
(2)第二种方式:
以上两种方式都可以查看程序的汇编代码。
查看寄存器信息
这样就可以查看当前运行环境的寄存器的使用信息。如果大家想对寄存器想有更加深入了解的话,可以看一下这篇博客👉函数栈帧的创建和销毁👈。相信看完之后,你会对函数栈帧、汇编代码和寄存器的了解会有一定的提升。
👉多动手,敢调试,才能有进步👈
- 一定要熟练掌握调试技巧。
- 初学者可能80%的时间在写代码,20%的时间在调试。但是一个程序员可能20%的时间在写程序,但是80%的时间在调试。
- 现在我们所学的都是一些简单的调试。 以后可能会出现很复杂调试场景:多线程程序的调试等。
- 多多使用快捷键,提升效率
👉一些调试的实例👈
实例一
实现代码:求 1!+2!+3! …+ n! ;不考虑溢出
#include <stdio.h> int main() { int i = 0; int sum = 0;//保存最终结果 int n = 0; int ret = 1;//保存n的阶乘 scanf("%d", &n); for (i = 1; i <= n; i++) { int j = 0; for (j = 1; j <= i; j++) { ret *= j; } sum += ret; } printf("%d\n", sum); return 0; }
这时候,如果我们输入3,期待输出9,但是实际输出的是15。
这是为什么呢?我们就要找出哪里出了问题了。
- 首先推测问题出现的原因。初步确定问题可能的原因最好。
- 实际上手调试很有必要。
- 调试的时候我们要心里有数。
调试的时候,一定要把监视窗口调出来,方便我们观察每一个变量的值。通过调试,我们可以发现,当 i = 1 , 2 的时候,都没有什么问题。但是当 i = 3 的时候,结果就出问题了。原来问题是处在求阶乘的时候,我们没有将 ret 重新赋值为1,从而导致 ret 还保留着上一次阶乘的结果,然后就出现了问题。在这里,就向大家演示了一次调试代码和找 BUG 的过程,希望大家能学会。
代码修改:
#include <stdio.h> int main() { int i = 0; int sum = 0;//保存最终结果 int n = 0; scanf("%d", &n); for (i = 1; i <= n; i++) { int ret = 1;//保存n的阶乘 int j = 0; for (j = 1; j <= i; j++) { ret *= j; } sum += ret; } printf("%d\n", sum); return 0; }
实例二
#include <stdio.h> int main() { int i = 0; int arr[10] = {0}; for(i=0; i<=12; i++) { arr[i] = 0; printf("hehe\n"); } return 0; }
上面代码的输出结果会是什么呢?我相信很多人,会说程序崩溃了,因为数组越界了。但是其实不是,而是死循环地打印 hehe。那为什么是这样子呢?请看下图:
上面的代码是有意为之的,大家平时一定不要这么写代码,避免数组越界。
👉如何写出好(易于调试)的代码👈
优秀的代码:
- 代码运行正常
- bug很少
- 效率高
- 可读性高
- 可维护性高
- 注释清晰
- 文档齐全
常见的coding技巧:
- 使用assert
- 尽量使用const
- 养成良好的编码风格
- 添加必要的注释
- 避免编码的陷阱
一个漂亮的示例
模拟实现strcpy函数
strcpy函数的原型如下:
char * strcpy ( char * destination, const char * source );
代码示例:
#include <stdio.h> #include <assert.h> char* my_strcpy(char* dst, const char* src) { assert(dst && src); char* ret = dst;//借助ret记住dst的首元素地址 while (*dst++ = *src++) { ; } return ret; } int main() { char arr1[] = "******************"; char arr2[] = "hello Joy"; printf("%s\n", my_strcpy(arr1, arr2)); return 0; }
1.assert的作用
assert是断言的意思,如果assert括号内的表达式为真,就什么事都不会发生。但是如果assert括号内的表达式为假,那么就会直接给你报错,哪行哪行出现了上面错误。见下图示例:
2.函数返回值类型的设计
其实上面的 my_strcpy 函数的返回值类型也可以是 void 类型,那为什么我们不把 my_strcpy 函数的返回值类型设置为 void 类型呢?因为将返回值类型设置为 char* 类型,可以实现函数的嵌套调用。
比如上面的printf("%s\n", my_strcpy(arr1, arr2));
语句,我可以直接将my_strcpy(arr1, arr2)
的结果打印在屏幕上。
3.’ \0 ’ 的拷贝
上面的 my_strcpy 函数还有几个需要注意的点,第一,就是 ‘\0’ 的拷贝。当数组arr2的内容全部拷贝到数组arr1后,my_strcpy 函数会自动在后面加上 ‘\0’,作为字符串结束的标志。那现在就来调试起来看一下,是不是这样子的。
演示代码:
#include <stdio.h> #include <assert.h> char* my_strcpy(char* dst, const char* src) { assert(dst && src); char* ret = dst;//借助ret记住dst的首元素地址 while (*dst++ = *src++) { ; } return ret; } int main() { char arr1[] = "******************"; char arr2[] = "hello Joy"; my_strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }
可以看到,my_strcpy将 ‘\0’ 也拷贝过去了,其实库函数strcpy也会将 ‘\0’ 拷贝过去。
第二个需要注意的点就是,使用 strcpy 和 my_strcpy 函数一定要确定目的数组的空间足以容纳源头数组的内容,否则将会出现一些意想不到的问题。
4.const的作用
我们可以看到,上面 my_strcpy 的第二个参数 char* src 用了 const 来修饰。那么用 const 来修饰指针变量有什么意义呢?先告诉大家结论。
结论:
conts修饰指针变量
- const 如果放在 * 的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可变。
- const 如果放在 * 的右边,修饰的是指针变量本身,保证指针变量的内容不能修改,但是指针指向的内容可以通过指针来改变。
举个例子:
#include <stdio.h> int main() { int n = 10; int m = 20; int const* p = &n;// const int*p = &n 等价于 int const *p = &n //*p = 100; //error,const放在*的左边,不可以改变指针指向的内容 p = &m;//可以改变指针变量本身的内容 printf("%d\n", *p); return 0; }
#include <stdio.h> int main() { int a = 10; int b = 20; int* const p = &a; //p = &b; //error,const放在*的右边,不可以改变指针变量本身的内容 *p = 100;//可以改变指针指向的内容 printf("%d\n", *p); return 0; }
👉常见的错误👈
编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定,相对来说简单。
这种错误是初学者最容易犯的错误,但是随着代码能力的提升,这种错误也会越来越少。
链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。
运行时错误
运行时错误是最难搞的错误,需要借助调试,逐步定位问题。
运行时错误可以由很多原因导致的,所以大家写代码的时候要细心一点。在这里就不跟大家讲解了,主要是借助调试来解决运行时错误。
温馨提示:
做一个有心人,积累拍错经验。
👉总结👈
本篇博客主要讲解了调试技巧、如何写出好的代码已经常见的错误等等。以上就是本篇博客的全部内容,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!!!💖💝❣️