写点什么

【C 语言】轻松解决 Bug

作者:泽En
  • 2022 年 3 月 05 日
  • 本文字数:3664 字

    阅读完需:约 12 分钟

【C语言】轻松解决Bug

什么是 bug?

bug:Bug 的原意:可以称之为“臭虫",现在指代的是漏洞,就是在安全系统上出现缺陷,攻击者可以对未授权的情况下进行攻击,Bug(计算机漏洞) 可以形容在各个领域范围之内所出现的漏洞。

臭虫:

  • 中文常称 BUG 为“缺陷”。而且,“缺陷”一词更能反映事情的本质。因为“臭虫”是从外面飞进去的,并非程序本身有问题。而程序本身存在的问题,是程序原来就具有的。因此,在这里将 BUG 翻译为“系统漏洞”更合适。

由来:第一代的计算机是由许多庞大且昂贵的真空管组成,并利用大量的电力来使真空管发光。可能正是由于计算机运行产生的光和热,引得一只小虫子(Bug)钻进了一支真空管内,导致整个计算机无法正常工作。研究人员费了半天时间,总算发现原因所在,把这只小虫子从真空管中取出后,计算机又恢复正常。后来,Bug 这个名词就沿用下来,用来表示电脑系统或程序中隐藏的错误、缺陷、漏洞等问题。


1945 年,计算机还是由机械式继电器和真空管驱动的,机器有房间那么大。体现当时技术水平的 MarkⅡ,是由哈佛大学制造的一个庞然大物。当技术人员正在进行不整机运行时,它突然停止了工作。他们爬上去找原因,发现这台巨大的计算机内部一组继电器的触点之间有一只飞蛾,这显然是由于飞蛾受光和热的吸引,飞到了触点上,然后被高电压击死。与 Bug 相对应,人们将发现 Bug 并加以纠正的过程叫做“Debug”(中文称作“调试”),意即“捉虫子”或“杀虫子”。

什么是调试?

调试其实就是找出 bug,计算机有 bug 一定是你程序的问题。所有发生的程序的问题都是有迹可循的,我们只需要顺藤摸瓜最后一步步解决。


一名优秀的程序员,必然是一名优秀的侦探。每一次的调试其实就是破案的过程。


调试:英文 DeBug,又称作是除错,是发现和减少计算机程序设备中和电子仪器错误的过程。

调试的基本步骤

  • 发现程序的错误所在:


  1. 程序员(写程序)

  2. 软件测试人员(测试程序的重要性)

  3. 用户/玩家


代价最小的是程序员,代价最大的是用户。


  • 以隔离消除的方式对错误进行定义

  • 确定错误的产生

  • 提出纠正的错误方法

  • 对程序的错误给予改正,并且改正完之后进行重新的修正

Debug 和 Release

Debug:

通常为调试版本,它包含的调试信息,并且不会做进行任何的优化,便于程序员的调试信息。

概念:

1937 年,美国青年霍华德·艾肯找到 IBM 公司为其投资 200 万美元研制计算机,第一台成品艾肯把它取名为:马克1号(mark1),又叫“自动序列受控计算机”,从这时起IBM公司由生产制表机,肉铺磅秤,咖啡研磨机等乱七八糟玩意儿行业,正式跨进“计算机”领地。为马克 1 号编制程序的是哈佛的一位女数学家格蕾丝·霍珀,有一天,她在调试程序时出现故障,拆开继电器后,发现有只飞蛾被夹扁在触点中间,从而“卡”住了机器的运行。于是,霍珀诙谐的把程序故障统称为“臭虫.............(BUG)”,把排除程序故障叫 DEBUG,而这奇怪的“称呼”,后来成为计算机领域的专业行话。从而 debug 意为排除程序故障的意思。

Release:

称之为发布版本,它往往是根据各种进行了优化,使得程序的代码都是最佳优的,以便让用户更好的去使用。

本质区别:

Debug 和 Release 编译方式的本质区别


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

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。还有大小的区别,因为 Debug 的版本是可以进行调试的(包含了相应的调试信息),而 Release 版本是不能进行调试的。


Debug 和 Release 的真正秘密,在于一组编译选项。


下面列出了分别针对二者的选项


(当然除此之外还有其他一些,如/Fd /Fo,但区别并不重要,通常他们也不会引起 Release 版错误,在此不讨论)

Debug 版本:

/MDd /MLd 或 /MTd 使用 Debug runtime library(调试版本的运行时刻函数库)


/Od 关闭优化开关


/D "_DEBUG " 相当于 #define _DEBUG,打开编译调试代码开关(主要针对 assert 函数)


/ZI 创建 Edit and continue(编辑继续)数据库,这样在调试过 程中如果修改了源代码不需重新编译 /GZ 可以帮助捕获内存错误


/Gm 打开最小化重链接开关,减少链接时间

Release 版本:

/MD /ML 或 /MT 使用发布版本的运行时刻函数库


/O1 或 /O2 优化开关,使程序最小或最快


/D "NDEBUG " 关闭条件编译调试代码开关(即不编译 assert 函数)


/GF 合并重复的字符串,并将字符串常量放到只读内存,防止 被修改


实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。


  • 采用 Debug 的话,如果用 Debug 代码来编译可执行程序的话,我们也会产生一个 Debug 的版本的一个可执行程序

  • 采用 Release 的话,就是发布版本,可执行的程序。——(简化)

Release 版本优化:

#include<stdio.h>#include<stdlib.h>int main(void){    int i = 0;    int arr[10] = {1,2,3,4,5,6,7,8,9,10};    for(i=0;i<=12;i++)    {        printf("hello C\n");        arr[i] = 0;    }    system("pause");}
复制代码


大家可以分别试一下就会发现不同之处了,分别用 Debug 和 Release 运行以下程序。这里是当你 for 循环的结果为假的时候,它的 i 又会重置为 0,然后一直这个样子重复循环打印 for 语句的循环内容。因为 arr 这里跟 i 它们两个人是同一块空间,那么当你改变 arr[i]的时候 int i 也会改变。因为它们的地址是一样的,所以才会改变。


由于这个程序涉及范围到栈区一些相关的知识点的内容,在这里我跟大家简单的说下。


栈区:栈区的默认使用是先使用高地址处的空间,然后在使用低地址处的空间。数组的下标随着增长,地址是由低到高变化的。当然如果你在不同的编译器上运行它的死循环的位置可能不同。你们也可以在不同的编译器上运行一下对比一下

运行与调试常用的快捷键

运行:Ctrl + F11

使用快捷键 Ctrl + F11 可以快速运行当前选中的资源文件。

调试:F11:是逐步 它是会进入函数的 把程序细化到步

shift + F11,跳出函数

F10 是逐行 在某条语句调用一个函数的时候 它不会进入(除非那里设有断点)

F9:切换断点

使用快捷键 F11 可以快速调试当前选中的资源文件。

断点调试操作:F5、F6、F7、F8

F5 和 F9 的快捷键可以进行配合使用。


在断点调试时,快捷键 F6 是执行下一步(单步跳过);F5 是执行当前调用内部细节步骤的下一步(单步跳入);F7 是跳过方法内部的执行步骤,直接返回到方法外的下一步(单步返回);F8 是跳出断点调试(跳过调试,继续执行代码,在下一个断点处暂停再次进入断点调试)。

查看程序的信息

ctrl + F5:开始执行不调试,如果你想让程序而不调试就可以进行使用。


自动窗口:当我们程序在执行中的时候,打开自动窗口,自动的把某些程序的上下文变量自动给你观察,这就是自动窗口的功能。


局部变量:和自动窗口差不多其实,但是它有一个缺点是它不能想监视谁就监视谁。


监视:监视这个作用就比较好了,当你想监视的话就输入那个变量的值,它就会呈现在你的监视当中,这样你就可以随时随地的去进行观察那个变量了。(监视窗口可以说是博主用的最多的了)


内存:如果我们想进行观察当前程序执行的内存的话,这个时候就是你输入观察的内存信息了。


(内存窗口也是用的可以说是非常多的了)


调用堆栈:在这里简单的和各位小伙伴们说下,像栈一样的逻辑给大家给展示出来它的一个逻辑,顶上往前放数据再往顶上出数据,可以很好反馈程序当中的代码的调栈的调用逻辑。


初学者掌握调试的内容可以达到事半功倍的效果,多多使用调试可以提高程序执行代码的效率。

如何写出好的代码?

优秀的代码

  • 代码整洁且规范

  • 注释清晰,且通俗易懂(代指写代码的人)

  • 可读性,有效性,可维护性

  • bug 尽量能够达到自己能够预期的范围之内,就是 bug 少,或者几乎没有 bug

  • 文档齐全,不乱去堆放

常见的 coding 技巧

  • 养成良好的编码风格

  • 注释必要的还是要去添加,提高代码的可读性

  • 避免编码的陷阱

  • 尽量使用 const

  • 使用 assert,头文件 #include<assert.h>,在 assert();里面的表达式如果为真的话,assert 表达式为真就什么都不会发生,如果 assert 的表达式为假,那么就会报错。


在这里简单的跟大家介绍一下 const 的关键字:const 修饰的数据是常类型,常类型的变量或者对象的值是不可以被该变的,也就是被它修饰过的是常量,相比之下 const 常量和 #define 是有点类似的,但是在我们用数组的时候还是要区分一下,因为数组里面 [常量表达式],此时我如果 const int n = 10 假设赋值给 arr 数组,int arr[n] = {0};结果是不行的,虽然加了 const 关键字,但实际上也只是变量只不过是不能被修改的变量。那么如果我们这里用的是 #define—定义符号常量就可以了。

assert 代码使用:

#include<stdio.h>#include<assert.h>int My_strlen(const char* pb){  int Count = 0;//计数  assert(*pb != NULL);//保证指针的有效性 NULL —— 空指针  while (*pb != '\0')  {    Count++;    *pb++;  }  return Count;}int main(void){  char arr[20] = "nihao C";  int ret = My_strlen(arr);  printf("arr(strlen) = %d\n", ret);  return 0;}
复制代码


最后,我想跟大家说程序遇到 bug 不要害怕,勇敢的去面对它吧,加油奥里给!



发布于: 刚刚阅读数: 2
用户头像

泽En

关注

好像没有😅 2022.01.29 加入

CSDN嵌入式领域新星创作者、2021年度博客之星物联网与嵌入式开发TOP5、2022博客之星TOP100 掘金创作者

评论

发布
暂无评论
【C语言】轻松解决Bug_C语言_泽En_InfoQ写作平台