想让bug远离你,当然是靠佛祖保佑~
/*
* **************************************************************************
* ******************** ********************
* ******************** COPYRIGHT INFORMATION ********************
* ******************** ********************
* **************************************************************************
* *
* _oo8oo_ *
* o8888888o *
* 88" . "88 *
* (| -_- |) *
* 0\ = /0 *
* ___/'==='\___ *
* .' \\| |// '. *
* / \\||| : |||// \ *
* / _||||| -:- |||||_ \ *
* | | \\\ - /// | | *
* | \_| ''\---/'' |_/ | *
* \ .-\__ '-' __/-. / *
* ___'. .' /--.--\ '. .'___ *
* ."" '< '.___\_<|>_/___.' >' "". *
* | | : `- \`.:`\ _ /`:.`/ -` : | | *
* \ \ `-. \_ __\ /__ _/ .-` / / *
* =====`-.____`.___ \_____/ ___.`____.-`===== *
* `=---=` *
* **************************************************************************
* ******************** ********************
* ******************** ********************
* ******************** 佛祖保佑 永远无BUG ********************
* ******************** ********************
* **************************************************************************
*/
本文完~
咳咳,直接开始开始正文~
本文的结构:
O,前言
一,什么是bug
二,什么是Debug
三,通用编译器的Debug
(1)打印输出
(2)单步调试
(3)设置断点
(4)监视与内存观察
(5)设置断言
四,编译习惯很重要
(0)从内存的视角写代码
(1)函数的高内聚低耦合
(2)要写注释
(3)适当留白
五,想要告诉你
O,前言
1.本文衔接栏目错误经验分享专栏的内容(开篇文章附在文末)
2.我们知道,编码时常见的错误有编译型错误,链接型错误,运行时错误等。本文的重点是运行时错误——具体说——你写出的代码并不能达到你想要的效果,但是你也不知道错在哪里。
3.Debug是我们知道有错误之后采取的行动,为了使程序达到预测的运行目标,我们不得不寻找自己写出的bug,但是这通常会花费很长的时间。如果我们可以在从源头上减少bug的产生,少写一些bug,或者不要为自己将来Debug设置那么多的障碍,那么这在无形之中节约了我们的时间,提高了效率。
一,什么是bug?
bug本意是“昆虫”或“虫子”,现在—般是指在电脑系统或程序中,隐藏着的一些未被发现的
缺陷或问题,简称程序漏洞。
"Bug”的创始人格蕾丝·赫柏(Grace Murray Hopper),1947年9月9日,格蕾丝·赫柏对Harvard Mark II设置好17000个继电器进行编程后,技术人员正在进行整机运行时,电脑突然停止了工作。于是他们爬上去找原因,发现这台巨大的计算机内部一组继电器的触点之间有只飞蛾,这显然是由干飞峨受光和热的吸引,飞到了触点上,然后被高电压击死了。
所以在报告中,赫柏用胶条贴上飞蛾,并把“bug”来表示“一个在电脑程序里的错误”。 于是,“Bug'这个说法一直沿用到今天。
二,什么是Debug
De- 是减少,消灭;调试的过程被称为Debug(消灭bug)。
可以通过隔离和屏蔽代码来大体定位问题,确定错误的原因,再修复代码,重新测试。
Debug也是一个版本,与它相对的是Release版本,两个版本各有不同的区别:
区别:
Debug内存占用较大,包含调试信息,相对于Release版本不做优化;
Release版本不含调试信息,由于做了相关优化,内存占用较小;
三,通用编译器的Debug
1.打印输出
使用printf函数跟随程序时时输出变量的值
e.g.1
编写一个程序,使其能根据输入得到符合要求的输出内容。
程序须包含一个函数,该函数能求出一个字符串中最长的单词并输出。
输入
一行可以包含 英文大、小写字母、空格 的字符串。
输出
通过调用函数,输出此字符串中最长的单词。
样例
标准输入 |
Einstein had been a famous physicist after then but he still wore the same old overcoat |
标准输出 |
physicist |
理解一下题,我们直接看代码:
#include<stdio.h>
int main()
{
char arr[50][50] = { 0 };
int brr[50] = { 0 };
int j = 0, p;
char ch;
for (;;)
{
p = 0;
for (; (ch = getchar()) != '\n';)
{
if (ch != ' ')//如果不是‘ ’则继续读取
{
arr[j][p] = ch;
p++;
}
if ((ch) == ' ')//如果是‘ ’则换到j的下一行
{
arr[j][p] = 0;
brr[j] = p;//(计数)
j++;
p = 0;
}
}
if ((ch) == '\n')
{
brr[j] = p;
break;
}
}
int tem = 0, k = 0;
for (; k < 50; k++)//找到最长字符串的长度
{
if (tem < brr[k])
{
tem = brr[k];
}
}
int m = 0;
for (; m < 50; m++)
{
printf("%d\n",m);//时时输出m,查看第几个字符串是最长字符串
if (brr[m] == tem)
{
break;
}
}//找到最长字符串对应的行数j
printf("%s\n", arr[m]);
return 0;
}
在最后一个for循环中,m用来记录是第几个字符串,用printf持续输出m,当m为5时,m == tem 即前面找到的最长字符串的行数。
于是,这段代码是题目的一种解法。
2.单步调试
在一些程序运行时,我们会看到报错信息,但是我们并不知道具体哪里出了错,这时候,我们能通过单步调试来定位出错的位置。
比如这些代码,当我们运行这段代码后,发现程序挂了,但是不知道具体哪一行出现了问题
e.g.2——当我们运行时,发现程序死循环了,但是由于在实际情况中,代码项目往往很复杂,我们一般无法直接目测处错误之处。
#include<stdio.h>
int main()
{
printf("haha\n");
main();
return 0;
}
e.g.3——当程序运行时,我们得知程序发生了数组越界,但是我们并不知道具体是哪一行代码导致发生了数组越界(因为我们必须找到代码,才能修改,改进代码)
int main()
{
char arr[10];
scanf("%s", arr);
return 0;
}
这时候是时候让单步调试出场解决问题了:
e.g.2解
当程序运行到main()时,再次按f10
程序就崩溃了
——得出结论:在第6行出现了问题
e.g.3解
当程序运行到scanf()后,再次按f10,程序报错
——得出结论:scanf()函数读入数据过多导致数组越界了
3.设置断点
在VS2019,我们用鼠标单击左侧的代码行的标号,就会出现一个红色的原点,这就是断点
设置断点:
鼠标单击代码行左侧的竖行,会出现断点;(再次单击取消断点)
将输入标稳定在要打断点的行,按f9,也会出现断点。(再次按f9取消设置)
使用断点:
断点与f5配合使用,每按一次f5程序会跳到逻辑上的下一个断点。
4.监视与内存观察
当我们调试的时候,我们想更直观的观察变量的值的变化过程,这时候我们可以打开监视
在按f10进入调试状态,注意是在调试状态下,我们在如图所示的窗口中,找到监视窗口:
(四个监视窗口都是可以使用的,第一次使用的话,可以随便选一个)
在监视窗口中,我们可以随时监视变量的变化过程:
内存窗口:
按图寻找,即可找到内存窗口
通过内存窗口,我们可以看到函数内变量在内存中的创建与销毁(附在文末)
因为之前分享过,在这里不再详细展开。
5.设置断言
assert()函数,用于在调试过程中捕捉程序错误
e.g.4——模拟实现strcpy()函数
我们不希望传入的p1,p2是空指针,对空指针解引用是很危险的!所以我们在此处使用断言assert,两个指针不是空指针
#include<stdio.h>
#include<assert.h>
void my_strcpy(char* p1,const char* p2)
{
assert(p1 != NULL);
assert(p2 != NULL);
while(*p1++ = *p2++)
{
;
}
}
int main()
{
char str1[20] = "xxxxxxxxxxxxxx";
char str2[] = "hello";
my_strcpy(str1,str2);
printf("%s\n", str1);
printf("%s\n", str2);
return 0;
}
(这段代码是非常巧妙的,它生动体现了C语言的紧凑性。)
如果是,那么程序会报出精确的错误信息:
assert()
所在头文件:<assert.h>
函数原型:void assert (int expression);
参数:expression即要检测的表达式
返回值:无返回值
assert() 的用法很简单,我们只要传入一个表达式,它会计算这个表达式的结果:如果表达式的结果为“假”,assert() 会打印出断言失败的信息,并调用 abort() 函数终止程序的执行;如果表达式的结果为“真”,assert() 就什么也不做,程序继续往后执行。
如果想要禁用这个断言函数,只需将
#define NDEBUG
定义在在代码的开头,在包含 <assert.h> 之前。
四,编译习惯很重要
(0)从内存的视角写代码
当我们写代码的时候,我们要有内存意识,看代码是内存,看数组是连续的线性表,如果是局部变量,则创建在栈区,地址先高后低等。
(1)函数的高内聚低耦合
我们写项目,往往需要实现多个功能,这时我们如果把所有的代码写在一起,那么一方面先显得逻辑不清,调试的时候不方便,为自己设置了障碍,另一方面,如果我们又要使用一个函数的某个功能,就又要重新写了。
如果我们将项目封装成一个一个功能不一的函数,在使用的时候调用,这样代码的逻辑性会有很大提高,并且可以减小代码量,提高效率。
(2)要写注释
-
解释代码:注释可以用来解释代码的意义和作用,有助于其他开发人员理解你的代码。
-
提高代码可读性:注释可以使代码更易读,更具有可读性。通过注释可以使代码更加易于理解,特别是在一些复杂的算法或逻辑代码中。
-
方便调试:注释可以用来记录程序的执行流程,这可以帮助错误诊断和调试。
-
方便维护:注释可以记录代码的变更历史和维护记录,方便以后维护和修改代码。
-
提高代码质量:注释可以强制编写人员思考代码的逻辑,有助于代码的质量提高。
(3)适当留白
适当的留白可以提升代码的可读性,便于理解。
五,想要告诉你
最好的Debug就是减少写出的bug,在写程序之前要根据内存的,数据结构的知识对代码的可行性与逻辑性进行估计,在心里构建出自己的想法的实现方法。
敢于发现并承认自己的错误
Debug是一个反复迭代的过程,需要不断地尝试和测试,直到找到并解决所有的错误。
加油!
编译时报错解决https://blog.csdn.net/2301_79465388/article/details/133919788?spm=1001.2014.3001.5502函数栈帧的创建与销毁https://blog.csdn.net/2301_79465388/article/details/134256464?spm=1001.2014.3001.5502
文章回顾
目录
O,前言
一,什么是bug?
二,什么是Debug
三,通用编译器的Debug
1.打印输出
2.单步调试
3.设置断点
4.监视与内存观察
5.设置断言
四,编译习惯很重要
(0)从内存的视角写代码
(1)函数的高内聚低耦合
(2)要写注释
(3)适当留白
五,想要告诉你
完~
未经作者同意禁止转载