C语言至此算是一个小结,相信大家对C语言已经有了一个初步的理解,所以C语言的初阶到这便结束了。学完C语言的初阶,大家应该能独自写出一段代码了。但是能写代码当然也就会存在bug(可不敢说我的代码就是标准实例,不可能有错误,大家还是要谦虚一点嗷,hahaha),有错误、有bug我们就要去把它找出来,这就需要用到本章的知识——代码调试技巧了。话不多说,正文开始!
- 什么是bug
- 调试是什么
- debug和release的介绍
- Windows环境的介绍
- 一些调试的实例
- 如何写出易于调试的代码
- 编程常见的错误
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
1.什么是bug
程序错误,即英文的Bug,也称为缺陷、臭虫,是指在软件运行中因为程序本身有错误而造成的功能不正常、死机、数据丢失、非正常中断等现象。 早期的计算机由于体积非常庞大,有些小虫子可能会钻入机器内部,造成计算机工作失灵。史上的第一只 "Bug" ,真的是因为一只飞蛾意外走入一电脑而引致故障,因此Bug从原意为臭虫引申为程序错误①
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
2. 调试是什么
引入
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧,就必然需要掩盖,那就一定会有迹象。迹象越多就越容易顺藤而上,这就是推理的途径。顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。一名优秀的程序员是一名出色的侦探,每一次调试都是尝试破案的
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
我们平时是怎么写代码的呢
是这样的吗
-- -- -- -- -- -- -- -- -- --
又是如何排查出现的问题的呢
还是这样去调试呢
你们是这样的吗~~(反正本长老有时候差不多,嘿嘿嘿)
但很显然,这并不是好的写代码习惯和调试的好习惯,我们应当拒绝迷信式调试!!!!
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
调试是什么
调试(Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程
程序调试是将编制的程序投入实际运行前,用手工或编译程序等方法进行测试,修正语法错误和逻辑错误的过程。这是保证计算机信息系统正确性的必不可少的步骤。编完计算机程序,必须送入计算机中测试。根据测试时所发现的错误,进一步诊断,找出原因和具体的位置进行修正②
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
调试的基本步骤
- 发现程序错误的存在
- 以隔离、消除等方式对错误进行定位
- 确定错误产生的原因
- 提出纠正错误的解决办法
- 对程序错误予以改正,重新测试
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
3.Debug和Release的介绍
调试版本
在调试版本下看一看可执行程序的大小
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
发布版本
想要看到发布版本,需要先转换成Release版本,再把你写得代码编译一下,这样才会在你的路径中生成一个Release版本的文件夹,将其打开可以看见:
可以发现,再Release版本中,可执行程序的(.exe/.e)文件的大小变小了。这是因为Release版本下,代码会被做一系列的优化,大小当然也会变小。而且Release版本中并不带有调试信息,即你不可以在这个版本进行调试(不信你可以按Fn+F10试试)
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Debug和Release反汇编展示对比
想要看汇编代码需要先Fn+F10开始调试,再右击鼠标点击”转到反汇编“
Debug环境下
Release环境下
对比可发现,发布版本下的汇编代码量明显少于调试版本的
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
那编译器进行了哪些优化呢
前面我们了解到,Release会在大小上进行优化,但其实Release还会在代码的功能上进行优化
先看一段代码,来猜一下结果
#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("shizhanglao\n");
}
return 0;
}
经过前面的学习,我们能够很容易的发现,在经过多次循环后(i>9之后),就会对数组进行越界访问,那么编译器是会报错的(比如本长老使用的编译器——vs2022)③。但从逻辑上来说,数组越界应该是错误访问,为什么会一直循环下去呢,这与代码运行时的内存分配有关
在以前的章节多次提到,计算机内存中有栈区、堆区和静态区,而局部变量、函数形参等都是在栈区中开辟储存空间的
不同的内存区域有不同的使用习惯
- 内存开辟在栈区的使用习惯是:先用高地址处的空间,再用低地址处的空间
- 数组的空间开辟使用习惯是:先用低地址,再用高地址
由于变量i先创建,所以在数组的上面,那么它们在内存中的分布就是这样的。因此,在数组越绝访问之后,是有可能会访问到i并把i的值改为0的(因为不确定越界访问两个元素够不够访问到i,可能i后面空了更多的字节才去创建数组arr)那么i又被改为0的话,循环条件将一直判断为真,不会再停下来,从而出现死循环(当然编译器很可能会察觉到这一点,并阻止你的越界访问)
那我们来看看这段有问题的代码再Release环境下的表现
代码竟然编译成功了,虽然在第九行仍然报了一个警告,但却没有再弹出报告错误的弹窗
此时我们再来看看两个变量的地址发生了什么变化,由于在Release环境下,代码不能编译,自然也不能使用监视和看内存,所以可以直接把地址打印出来
运行后可以看到,在Release环境下,编译器居然把i和arr的地址给换了,把变量i创建的位置放在了数组arr的后面(因为i的地址比arr的地址小了,i在低地址处了)
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
附
①想要进一步了解Bug的概念,可以看看Bug的百度百科链接
②想要进一步了解调试的概念,可以看看程序调试的百度百科链接
③因为编译器会报错,当然就无法演示死循环的具体表现了