目录
前言:
一、Bug:
二、调试:
1.调试是什么:
2.调试的基本步骤:
3. Debug 与 Release :
三、在Windows环境下进行调试:
1.调试环境的准备:
2.调试的快捷键:
3.调试查看程序当前信息:
①.查看临时变量的值:
②.查看内存信息:
③.查看调用堆栈:
④.查看汇编信息:
⑤.查看寄存器信息:
四、如何写出好的代码:
五、编程常见错误:
1.编译型错误:
2.链接型错误:
3.运行时错误:
六、总结:
前言:
在前面的十九篇文章中,我们较为系统的学习了C语言的各种语法结构,相信此时的各位小伙伴们已经有能力写出一些简单的C语言程序了。而今天我将要带领各位小伙伴们学习初阶C语言的最后一课——调试。并且今天的课程较为轻松,主要是为大家介绍一些实用的调试小技巧,能希望能对大家的代码查错和调有所帮助。
一、Bug:
作为一名程序员,bug这个词应该是耳熟能详。相信你们在平时在身边小伙伴们的口中也一定常常听到这个词汇,那么bug到底是什么呢?它又是如何产生的呢?
英文的 Bug,即臭虫,翻译成中文为程序缺陷,是指在软件运行中因为程序本身有错误而造成的功能不正常、死机、数据丢失、非正常中断等现象。
而程序错误之所以被称为“臭虫”,还要从一位老奶奶说起。这个人,就是与阿兰·图灵、史蒂夫·乔布斯、比尔·盖茨等一同入选“IT界十大最有远见的人才”的唯一一位女性——格蕾丝·赫柏。
1906年,赫柏出生在美国纽约。从上学起,赫柏在数学、物理方面都异常出色,一路顺顺利利直到16岁参加高考,却因为偏科太严重,拉丁文考试不及格,没能考上大学。复读一年后,赫柏考上韦莎学院。这所大学在2012年《福布斯》公布的美国最好大学中排名第20位,新闻报道更是将它评价为最值得选择的大学。毕业时,赫柏不仅同时获得数学、物理学位,还获得美国优等生的荣誉,留校担任教师的她被聘为学院副教授。赫柏的曾祖父是一名海军将军。1939年二战爆发时,满怀爱国热情的赫柏也坚决要求加入海军,毕业的赫柏因为出色的数学背景,被分配到美国船舶局位于哈佛大学的战时科研中心。彼时,军方正在开展世界第一台大型数字计算机的研究项目——马克一号。赫柏被任命为著名计算机专家霍德艾肯博士的助手,成为这个项目的第三名程序员。一天,计算机发生故障,赫柏经过排查,在计算机的继电器触电里,找到了一只被夹扁的小飞蛾,这只小虫子卡住了机器的运行,赫柏顺手将飞蛾夹在工作笔记里,并诙谐的把程序故障称为“bug”。这就是我们今天最爱说的“bug”的由来。它的意思,和原身一致,真就是“一只臭虫”。
这一称呼后来演变成表达缺陷漏洞的计算机专业术语,人们习惯地把排除程序故障叫做“debug”(除虫)。
二、调试:
一名优秀的程序员也是一名出色的侦探,每一次调试都是一次破案的过程。所有要发生的事情都一定有迹可循,问心无愧就不存在痕迹,自然无需遮遮掩掩,倘若问心有愧,则必然遮掩一番,一旦遮掩就一定会留下痕迹,而留下的痕迹越多,就越容易顺藤而上,这就是推理的途径。顺着这条途径顺流而下就是犯罪,逆流而上,则是真相。
那么我们是怎么写代码的呢?我相信很多小伙伴们在写代码时都是这样的:
可是编写代码一股脑闷着写就可以了吗?答案自然是不行。这样不管不顾的去编写代码,最后一般会产生两个经典问题:这程序为什么跑不起来?这程序又是怎么跑起来的呢?
当遇到这样的问题的时候,就需要我们对程序进行调试,来验证和查看我们程序的每一个运行细节。
1.调试是什么:
调试(英语:Debug,原意为除虫),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。
调试是一个多步骤的过程,包括识别问题、隔离问题源,然后纠正问题或确定解决问题的方法。在软件开发中,调试涉及定位和纠正计算机程序中的代码错误。调试过程在编写代码后立即开始,并随着代码与其他编程单元组合形成软件产品而在连续阶段继续进行。通过使用单元测试、代码审查和结对编程等策略,可以使调试过程更容易。
2.调试的基本步骤:
· 发现程序错误的存在
· 以隔离、消除等方式对存物进行定位
· 确定错误产生的成因
· 提出纠正错误的解决办法
· 对程序错误予以改正,重新测试
3. Debug 与 Release :
我们的 VS 2022 内置有两种编译版本,分别是 Debug 版本与 Release 版本,两种版本村踩着一定的差异。
Debug 版本通常称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序。
Release 版本通常则被称为发布版本,它往往会对我们的代码进行各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好的使用。
这两种版本在很多地方都存在着差异,我们以下面这段代码为例:
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
为了进行演示,我们故意写出了一个bug程序,即在进行数组元素访问时,使其越界访问。
首先我们来看本地文件的差别(上为 Debug 版本,下为 Release 版本):
我们可以清楚的看到,上面的 Debug 模式没有对我们编写的程序进行优化,存放在本地的 exe 文件大小为40KB;而下面的 Release 模式对我们的代码进行了优化,使其缩小为仅仅7KB。这仅仅是一个只有10行不到的代码,就会产生近六倍的差距,我们可以大胆想象一下如果我们的代码有上百万行,那么程序大小的差异会有多大。
接着我们来看实际的程序运行结果(上为 Debug 版本,下为 Release 版本):
我们可以看到,Debug 版本没有优化我们的程序,导致我们写出的 bug 代码原封不动的被执行,在越界访问时造成了程序的死循环;而同样的代码经过 Release 版本的各种优化,我们看到我们的 bug 程序在运行时没有陷入死循环,而是被及时的制止了。
最后我们来看看两种版本下的反汇编代码的差异(上为 Debug 版本,下为 Release 版本):
我们可以看到,反汇编代码清晰的展示了两种不同版本下执行的空间、时间效率的差异,显而易见的,Release 版本在优化上远远优于 Debug 版本。
那么既然 Release 版本这么好,我们为什么还要介绍 Debug 版本呢?甚至于说,后文主要就是针对 Debug 版本进行的讲解呢?原因是,★ Debug 模式支持调试而 Release 版本不支持 ★。
三、在Windows环境下进行调试:
1.调试环境的准备:
首先我们要在环境中选择 Debug 版本,才能使代码正常调试:
2.调试的快捷键:
在我们的调试过程中,需要用到许多调试功能:
为了便于我们更高效的调试,我们有必要记住这些常用的调试快捷键:
★ F5 :启动调试,常用来跳至下一断点处。
★ F9:创建断点与取消断点。断点的作用为,可以在任意程序语句处打上断点,可以使程序在运行至断点处后暂停执行,便于我们接下来一步一步地执行,方便我们对程序细节进行观查。
★ F10:逐过程。通常用来处理一个过程,一个过程可以是一次函数调用,也可以是一条语句。
★ F11:逐语句。即每次执行一条语句,但是这个快捷键可以使我们的执行逻辑深入到函数内部去(最常用)。
★ CTRL+F5 :开始执行而不调试。如果你型让你的程序直接运行起来而不进行调试就可以直接使用。
3.调试查看程序当前信息:
①.查看临时变量的值:
我们通过 调试 => 窗口 => 监视 ,可以打开监视窗口,在窗口内输入你想要查看的变量名,即可在监视窗口内查看到该临时变量在程序运行时产生的实时变化了(调试状态下):
②.查看内存信息:
同样的我们通过 调试 => 窗口 => 内存,可以打开内存窗口,在窗口内输入你想要查看的变量的地址(或取地址操作符+变量名),即可在内存窗口内查看到目标对象的内存使用在程序运行时产生的实时变化了(调试状态下):
③.查看调用堆栈:
也还是一样,我们通过 调试 => 窗口 => 调用堆栈,可以打开调用堆栈窗口,不同的是该窗口无需输入,会按照当前正在执行的语句位置,反馈出当前程序的堆栈调用情况(调试状态下):
④.查看汇编信息:
我们一般有两种方式去查看汇编信息,第一种是在调试状态下,与上面相同的方式,通过 调试 => 窗口=> 反汇编,打开汇编窗口;而第二种方式是在调试状态下,直接单击鼠标右键,选择转到反汇编,即可打开汇编窗口。这两种方式均可以打开汇编代码窗口,来查看我们写出的代码对应的汇编代码(调试状态下):
⑤.查看寄存器信息:
通过 调试 => 窗口 => 寄存器,打开寄存器窗口,可以查看当前运行环境的寄存器使用信息(调试状态下):
四、如何写出好的代码:
好的代码通常都是易于调试的。优秀的代码通常都具有运行正常、bug少、效率高、可读性高、可维护性高、注释清晰、文档齐全等优点。
而常用的 coding 技巧主要有使用assert、尽可能使用const、使用好的代码注释风格、添加必要的代码注释、避免编码陷阱等等,再者就取决于程序员自己的亲身代码编写经验了。
同时各位小伙伴们在编写代码时应当注意,我们在编写程序代码时,要仔细认真的分析参数以及返回值类型的设计,尽最大可能避免空指针、野指针对我们程序运行造成的危害等等容易出现错误的地方。再者也就是及时的为我们的代码添加上合适的注释,既便于我们在调试阶段检查我们代码的逻辑性错误,也能大大提高我们代码的可读性与可维护性。
在这里再送给各位小伙伴们一张 “ 没有BUG符 ” ,祝愿各位小伙伴们今后在编写程序代码时:一看就会,一写就对,没有BUG,令人陶醉!
五、编程常见错误:
1.编译型错误:
这种类型的错误最为常见,我们可以直接通过查看错误信息提示(双击)解决问题,或凭借我们的代码编写经验,也很容易就能解决,相对来说较为简单。
2.链接型错误:
这种类型的错误要稍微难处理一点,我们的处理方法主要是通过查看错误提示信息,在我们的代码中找到错误信息种的标识符,然后逐步调试定位问题所在。这类问题多出现于标识符名、不存在、不符和或存在拼写错误。
3.运行时错误:
这类问题最为麻烦,因为它们既在编写语法上没有问题,也不存在拼写错误,而一般常见于我们所编写出来的程序的逻辑漏洞。即看上去没有问题,但在执行时得不出我们想要的执行结果。面对这类问题,我们只能通过借助调试,逐步定位问题,慢慢解决。
六、总结:
到这里,我们关于初阶C语言的学习就全部结束了。在这里我非常感谢各位小伙伴们这么久以来对我的鼓励与支持,每次写到想要放弃时总会有小伙伴私信我为我加油鼓劲。如今我们的学习之旅暂时是告一段落了,不过学习之路还远远没有完结。接下来我会休息几天,调整调整状态,便会开始下一阶段的学习讲解,希望到时候还能看到各位小伙伴们熟的身影。
希望这些日子写出的一些文字能对各位小伙伴们有所帮助,用心了它们是知识、是力量,不够用心那它们也不过是一堆冰冷的文字罢了。成功是陡峭的阶梯,两手插在裤袋里是爬不上去的,当你想要放弃的时候想想当初为什么坚持到这里!
新人初来乍到,辛苦各位小伙伴们动动小手,三连走一走 ~ ~ ~ 最后,本文仍有许多不足之处,欢迎各位看官老爷随时私信批评指正!