正片开始👀
Bug👏
bug意为臭虫,计算机术语里就是幺蛾子,对,你的程序又出幺蛾子了。为什么要叫bug?关于这个还有段有趣的历史
有一天赫柏正愉快地敲着Mark Ⅱ的代码时,计算机突然就停止运作了,那时的计算机远不如现在小巧,赫柏他们只能一个个排查计算机庞大的处理器群,经过一段时间的排查后停机原因终于被找到了。原来是一只飞蛾被计算机的光和热吸引,触发了电脑的短路,当然这只可怜的飞蛾也一命呜呼了按理说一般人也就是把飞蛾拿走,然后重启下电脑也就完事了,但赫柏显然不是一般人她小心翼翼地把这只飞蛾拿了下来,然后把它工工整整地粘在了记事本上… …
这就是历史上第一个 bug 的诞生。
调试的重要性👏
我估计前期我们找 bug 都是用眼睛瞅,特别是我们这种大一的刚接触的,现在还好,到了以后需要写大工程的时候,眼瞅不头疼的才是大哥,对于一个成熟程序员20%时间写代码而80%时间在调试代码。
我们写代码就是一个推理的过程,整个流程的正确与错误都是有迹可循的,推理的途径就是这些迹象。
一名优秀的程序员就是一个优秀的侦探,找到迹象,顺流而下是错误,顺流而上是真相,调试就是我们破案的过程。
调试基本步骤👏
1.找错(进行隔离,消除来定位错误)
2.知道错因
3.寻找解决办法
4.纠正,重测
Debug与Release👏
Debug(Debugging),即排错,称为调试版本,不作任何优化,包含调试信息,便于我们调试程序。
Release ,即释放,成为测试版本,往往进行各种优化,让代码在大小和运行速度上都是最优的,面向用户,可以很好的使用。但是!注意Release版本是没法进行调试的,这种观点仅限于我当前知识面的限制,实际上Release也是可以的,下面是大佬对我的指正意见:
快捷键👏
在调试过程中,掌握一些快捷键会大大增加我们的效率。以vs2019为例,我们先会设置断点如图(行标左侧设置)
断点设置在需检查代码的任意位置,运行到这一步就会停下给我们报告。断点完F5调试,执行窗口弹出后就会发现调试就会出现更多内容,我框出来的在之前记录C语言学习时都有用到。
注意F5是调试,Ctrl+F5是运行,通常会使用会F5跳到想要的断点处,有些电脑上比较装怪,快捷键没反应的,建议多按一个Fn键试试,Fn是功能辅助键,相当于一个开关,本质上 F5+Fn = F5。需要强调的是逐语句和逐过程,如果你想看每个细节,不放过每一个角落就用逐语句,两者的力度是不一样的,逐过程会跳过代码里的函数部分。
vs玩家重点推荐 Ctrl+k+c,注释选中行;Ctrl+k+u,取消注释,熟练运用会很方便。
其余还有很多不赘述,下面准备了超全的实用快捷键用法:
想深入了解的铁子戳这里
除了用调试验证代码的正确性,还可以用于研究具体的问题,举个栗子:
这道题是Nice的面试真题
请说明下面代码是否能正常运?运行结果是什么?为什么会出现这个结果? int main() { int i = 0; int arr[10] = { 0 }; for (i = 0; i <= 12; i++) { arr[i] = 0; printf(“hehe\n”); } return 0; }
这里当我们打开调试窗口直接开调:
OMG,是一样的。但我们这里的调试只能看到现象,他底层的原理我们要自己思考。
其死循环的逻辑大致是这样的,我们创建了一个变量i,arr,他们都是局部变量,而局部变量时放在栈上的,栈区上内存使用习惯是先使用高地址存储空间,再使用低地址。这里注意,我们开始给的十个大小的空间,i的变量是到12,这里明显是越界访问,但为什么没有报错停下来?结合我们刚刚监视的结果,我们再把格局打开:
数组随着下标的增长,地址是由低到高的变化,在我数组适当越界时,如果i和arr之间的空间适当的话,就有可能使arr向后越界时就访问到了i,造成了循环变量的i改变,最终会死循环。
这种错误其实存在偶然性,首先i和arr[12]相同,只是恰巧,但如果我把i换成11,结果就大相径庭了,我只形成了越界但没有改变循环变量i的值。其次,这个代码是严重依赖环境的,比如在VC 6.0里面i和arr就是连续的,gcc里面i和arr之间有一个空间。
打趣的是,我们在Release版本里面是不会报错并且会停下来,其实在刚刚的截图里面也是会报错的,但是!死循环停不下来,他根本没时间来报错。Rlease的优化并不是万能的,不要期待利用Release版本来掩盖代码的bug,最好的做法就是不要越界。
如何写出易于调试(优秀)的代码👏
1.硬性要求运行正常
2.bug少(估计没人敢保证零bug吧)
3.效率高
4.可读性高
5.可维护性(容易修改与二创)
6.注释(方便阅读)
7.文档齐全
常见的coding技巧👏
1.使用assert
意为断言,在代码执行前设的前哨,比如我们函数传参时,当我传的内容变成空指针,后面如果函数进行解引用操作,对于空指针解引用是会造成程序崩溃的,是很危险的,所以我们用assert当监护人能快一步该诉我们问题在这里;设置assert也是一个好习惯,面试官见了直呼老司机!
# include<assert.h> void my_str(char* a, char* b) { assert(a != NULL && b != NULL);//断言 while (*b != '\0') { *a = *b; b++; a++; } *a = *b; } int main() { char arr[10] = {0}; char arr2[] = "bit"; my_str(NULL, arr2); // 故意设成NULL程序会崩溃 printf("%s\n", arr); return 0; }
效果如上图就会显示断言失败。
2.尽量使用const
const我之前博客写过的常变量修饰符。
就上面模拟strcpy函数,如果有天有个内鬼改了你的代码,写成 *b = *a,就拷反了,编译器也会傻不拉叽的输出,尽管结果什么都没有。那怎么办呢?我在开头就定义好
char* my_str(const char *a,const char* b)
1
这样不管你咋改,我 *a,*b都是无法改变的。
注意const int *p=&a , const 在 * 左边时,修饰的是指针指向的内容,指针变量不影响 ;在 * 右边是,修饰指针本身,指针变量不能修改,其内容可以通过指针来改变。
3,形成良好的编码风格
4.注释!注释!注释!(好习惯讲三次)
5.避免编码陷阱
今天就到这里了,摸了家人们。