避免bug实用调试技巧(和bug郭一起学C系列)

简介: 避免bug实用调试技巧(和bug郭一起学C系列)

什么是bug

bug原意是“臭虫”,现可用来指代计算机上存在的漏洞,原因是系统安全策略上存在的缺陷,有攻击者能够在未授权的情况下访问的危害。广义上,bug可用作形容各领域范围内出现的漏洞或缺陷。-摘自百度


bug翻译过来就是虫子的意思,那为啥会用来指代计算机的漏洞呢?

第一台计算机的bug

image.png

1946 年,霍普发现了第一个电脑上的 bug。

这是计算机第一个bug,也是最大的一个bug。

在 Mark II 计算机上工作时,电脑不能正常运作了,霍普和整个团队都搞不清楚为什么。

后来才发现,是一只飞蛾意外飞入了一台电脑内部而引起的故障。终于把问题解除了,霍普在日记本中记录下了这一事件。

这是第一台计算机出现漏洞时发现的虫子,也是第一个bug,因为这只虫子导致计算机停止了工作。

这便是bug的由来!


博主bug郭的由来

为啥我会叫bug郭,因为博主经常写几行代码,就有一页报错,有时候比较幸运,编译没有错误,运行却出现bug,所以我就是bug附体了!


调试什么

往往事情都是有迹可循,不可能天衣无缝,如果顺着迹象往下便是犯罪,往上顺藤摸瓜便是破案,找出真相!


简单的说调试就是找bug的过程!

就如同警察办案一样,逐渐将真相揭露找到凶手的过程就是调试!


调试的重要性

编程入门者百分之80的时间在写代码,百分之20的时间调试。

而大佬写百分之20时间写代码,百分之80的时间调试。




我写几行代码便需要找半个小时的bug,这样说我有当大佬的潜质!


调试的重要性!

image.png

调试步骤

发现bug

你要知道你的程序有bug

定位

大概知道,bug出现的代码区域位置。

找到bug

经过你的一番查找,然后找出bug把找到!

改bug

你需要指定一个方案,将出现bug的代码修改。

重新运行

运行更改后的代码,发现是否成功!

学会这5步,让你不在迷行调试。让bug无处躲藏!



Debug和Release区别

在调试之前不得不介绍一下Debug和Release区别

image.png

可以看到我们VS配置管理器下面有两个版本,Debug和Release这两个版本有什么区别呢?

Debug


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


Release


Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优

的,以便用户很好地使用。


代码:


#include<errno.h>
#include<stdlib.h>
int main()
{
   //打开文件
   FILE* pf = fopen("data.txt", "w");
   if (NULL == pf)
   {
    perror("fopen");
    return -1;
   }
   //读文件,随机读写
   // 
   //关闭文件
   fclose(pf);
   pf = NULL;
   return 0;
}

image.png

可以看到一个程序在不同的版本下运行后,生成的程序文件大小不同,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] = i;
  }
  return 0;
}

Debug报错

image.png

Release运行成功!


image.png

我们一起分析一下这个代码,这个代码明显有问题,为啥在Release下编译过去了呢?

这是因为Release进行的优化,变量的储存位置也不一样。

这个代码的具体问题会在调试案例中具体讲到!



调试快捷键

image.png


F5:启动调试,经常用来直接调到下一个断点处。


F9:创建断点和取消断点 断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得程序在

想要的位置随意停止执行,进而一步一步调试下去。


Ctrl+F5:直接执行不调试,如果想让程序运行起来不调试。


F10:逐过程,通常是一条语句或者一个函数,不会进入函数体内部。


F11:逐语句,一条语句,一条语句执行,会进入函数体内部执行。


这些是VS下面最基础的一些调试快捷键,通常这些快捷键配和使用,效果更佳!


调试窗口

image.png

执行调试的时候才能看到调试窗口!


在调试,进行后该如何发现问题,找到bug呢?

调试窗口,是我们破案的工具,是我们的第三只眼睛!

通过调试窗口,你可以观察代码的走向,是否和你预期的值一样。

调试的时候观察变量信息

查看变量的值

image.png

这里有3个窗口,监视,自动窗口,局部变量,及时。

监视: 可以通过输入变量,一直观察变量的值

自动,局部窗口: 随着代码调试的执行不同时刻不同变量的值会出现。

通常我们常用的是监视窗口,一直监视变量的值,看它随着调试的逐步执行,是否和我们预期的一样!


内存窗口

你可以通过内存窗口,输入你想观察变量的地址查看,它的地址和储存。

image.png

&input便出现了input的地址和存储。还有很多窗口,我就不一一介绍了,自己多多尝试,多多进步!

image.png

发现bug

编译出错

这是最简单的一步,当你的代码报了警告,错误,如果编译不过去这边是bug。

而这种是最低级的bug,只需要,根据错误,将指定代码行更改即可!


image.png


运行出错

运行错误,是最令人头疼的,当你的代码,经过一番更改,没有错误,警告,运行后出现了控制台,你准备欢呼的时候,程序突然崩了,或者结果并不是自己预期的结果,显然很让人头疼。

image.png

这时候不要慌,问题不大,bug郭带你调试!


定位bug

找到bug出现的大概位置。

如何找到bug具体出现的位置呢?

养成写代码的好习惯,写一点编一点,不要一顿操作写了几千行代码,然后运行一下,啪,出现一页bug这时整个人都麻了。

如果我们写一点,编一点,就可以将问题定位到,具体的代码块!

你可以通过F9设置断点,辅助F5跳转执行到断点处。

将光标移动到你要创建断定的位置行,按住快捷键F5创建了断定,通过F5执行到断点处,再通过,F10或者F11执行下去!


image.png

找到bug并更改bug

在通过你敏锐的调试侦查后,你便找到了bug。bug相信你一定可以的!


调试案例练习

案例 一:


#include <stdio.h>
int main()
{
    int i = 0;
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    for(i=0; i<=12; i++)
    {
        arr[i] = 0;
        printf("hehe\n");
    }
    return 0;
}

image.png

这个代码居然死循环了,为啥会发生这样的事情,这时就得上调试,干他!


当i=12时

image.png

奇怪的事情发生了,i居然变成了0。

估计这个bug得研究一整天!

不过bug郭带你研究!


首先为啥程序不会奔溃呢?数组都越界访问了?

因为程序在死循环中来不及奔溃!

为啥突然i就变为0了?

当程序执行到arr[12]=0时,arr[12]和i在同一块空间,所以i的值变为了0。

image.png

案例二:


求阶层1!+2!+…+n!的和


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时,预期结果:

1+2+6=9;

but运行却是15

image.png

你可以调试一下,发现问题出在哪里。

如果你代码量够,不用调试便可以发现问题,但是这是一个调试案例,自己调试一下!


如何避免Bug

为啥我们要调试找bug呢,就不能避免bug吗?

优秀的代码:


代码运行正常

bug很少

效率高

可读性高

可维护性高

注释清晰

文档齐全

让我们看看vs函数库中的优秀代码如何实现的

示范:


模拟实现库函数:strcpy
char * strcpy(char * dst, const char * src)
{
        char * cp = dst;
        assert(dst && src);
        while( *cp++ = *src++ )
        {
        }
        return( dst );
}

看后是不是不由自主的赞叹一句,秒


常见的coding技巧:


使用assert

尽量使用const

养成良好的编码风格

添加必要的注释

避免编码的陷阱。

assert


断言,通常用来,判断expression是否为FALSE(假)

如果是便程序停止。

而代码库里代码assert(dst && src);

便是判断dst和src是否为空指针。

空程序便报错,精准定位bug


const


我们之前已经知道const关键字修饰变量,该变量便具有了常属性,无法被更改。


const在*的右边,该指针变量无法被改变

char * const src说明src(指针)无法被更改。

const在*的右边,该指针指向的变量无法被改变

const char * src说明*src(变量)无法被更改

学到了吗?

我们来试试模拟实现一下strlen函数


#include <stdio.h>
int my_strlen(const char *str)
{
    int count = 0;
    assert(str != NULL);
    while(*str)//判断字符串是否结束
    {
        count++;
        str++;
    }
    return count;
}
int main()
{
    const char* p = "abcdef";
    //测试
    int len = my_strlen(p);
    printf("len = %d\n", len);
    return 0;
}

是不是很perfect!


今天的调试小技巧就学到这里,下去多动手调试调试!


博主在这里祝愿大佬们写的代码没有bug,最好的祝福,送给兄弟们!


目录
相关文章
|
SQL JSON 前端开发
【改BUG】项目遇到的奇葩bug
【改BUG】项目遇到的奇葩bug
|
22天前
|
SQL 运维 Java
记一个折磨了我一天半的 Bug
一杯茶,一根烟,一个 Bug 一天根本改不完。
28 1
|
5月前
|
JavaScript Java
做小程序时遇到的bug
做小程序时遇到的bug
|
6月前
|
开发者 C++ UED
你以为的Bug VS 实际的Bug:解密程序开发中的意外之旅
作为开发者,我们在日常开发过程中经常会遇到各种各样的Bug,有些Bug可能很容易发现并解决,但也有一些Bug让人感到困惑摸不到头脑,甚至是无厘头Bug,就像我们以为的Bug与实际的Bug之间的差异一样,让人头大。所以我们在日常开发过程中,一定要细心、细致、细顾,在面对任何Bug的时候都要抱着敬畏的心态去解决,因为我们永远不知道在实际程序开发中的意外是啥,有什么意外在等着我们去发现和解决。那么本文就来讨论分享一下开发者在工作过程中遇到的“你以为的Bug”与“实际的Bug”之间的差异在哪里?,然后通过一个有趣的比喻,我们将深入分析这些不同类型的Bug,还有就是在解决问题时的重要性和挑战。
75 1
你以为的Bug VS 实际的Bug:解密程序开发中的意外之旅
这短短 6 行代码你能数出几个bug?
这短短 6 行代码你能数出几个bug?
48 0
|
JavaScript 前端开发 IDE
因为使用peerDependencies而引发的bug
因为使用peerDependencies而引发的bug
因为使用peerDependencies而引发的bug
|
Python
遇到bug不要慌,先发个文章看看
遇到bug不要慌,先发个文章看看
128 0
|
编解码 Java 数据库连接
|
C++
学会调试,让你也成为改bug能手
学会调试,让你也成为改bug能手
108 0
学会调试,让你也成为改bug能手
|
架构师 测试技术 程序员
程序中的Bug是如何产生的?
  ★   Bug,总是令人讨厌的东西。那Bug是如何产生的呢?作为高级软件架构师和软件测试工程师的易哥将在这篇文章中解答这个问题。   ”   说起Bug,大家都认为它是被“写”出来的,即主要在开发阶段产生。   但其实Bug的产生最有可能是在需求阶段(意外吧!这是有统计数据证明的),且在需求阶段产生的Bug影响最大。当然,在设计、开发、使用阶段也会出现Bug。   接下来我们详细了解下Bug的相关知识。
847 0