还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧.

本文介绍了什么是bug,什么是调试,调试重要性,如何调试解决bug,各种常用的调试快捷键,如何写出好代码以及const关键字,assert断言库函数介绍,写代码各种遇见的错误, 调试训练

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第1张图片

实用调试技巧

  • 一.什么是bug
  • 二.调试是什么?有多重要?
    • 1.调试是什么?
    • 2.调试的基本步骤.
    • 3.debug和Release的介绍
  • 三.Windows环境调试介绍.
    • 1.调试环境的准备
    • 2.学会使用快捷键
    • 3.调试的时候查看程序当前信息
      • ①查看临时变量的值
      • ②查看内存信息
      • ③查看调用堆栈
      • ④查看汇编信息
      • ⑤查看寄存器信息
  • 四.多多动手.尝试调试.才会有进步!
  • 五.如何写出易于调试的代码.
    • 1.优秀的代码
    • 2.常见的coding技巧
    • 3.模拟实现库函数strcpy(字符串拷贝函数)
    • 4.assert断言函数
    • 5.const 关键字介绍
  • 六.编译常见的错误
    • 1.编译型错误
    • 2.链接型错误
    • 3.运行时出错
  • 七.学以致用,调试训练
    • 1.分析代码,找出问题
    • 2.对代码出现的问题图解
  • 八.总结

一.什么是bug

程序错误,即英文的 Bug ,也称为 缺陷 、 臭虫 ,是指在软件运行中因为程序本身有错误而造成的功能不正常、 死机 、数据丢失、非正常中断等现象。

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第2张图片

上图,这只虫子是世界上出现的第一个计算机bug,
事件发生在1947年9月9日,葛丽丝·霍普(Grace Hopper)发现了第一个电脑上的bug。当在Mark II计算机上工作时,整个团队都搞不清楚为什么电脑不能正常运作了。经过大家的深度挖掘,发现原来是一只飞蛾意外飞入了一台电脑内部而引起的故障(如图所示)。这个团队把错误解除了,并在日记本中记录下了这一事件。也因此,人们逐渐开始用“Bug”(原意为“虫子”)来称呼计算机中的隐错。 —— 维基百科

在我们用编程语言写源程序时,在写程序过程中,bug也就难免会出现了,有什么办法自己写的程序一定没有bug呢?
简单,不写程序就没有bug了~~(这是不现实的)…
在程序员的世界里,每天都在和bug打交道,可以说不是在找bug,就是在找bug的路上…
而今天要讲的就是如何通过编译器精确找bug 的技巧,也成调试技巧.

二.调试是什么?有多重要?

.每个bug出现都是有迹可循的,就像我们生活中 所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没迹象了,
如果问心有愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。
顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
而一名优秀的程序员是一名出色的侦探。每一次调试都是尝试破案的过程

1.调试是什么?

调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。俗称找bug的过程.

2.调试的基本步骤.

1.发现程序错误的存在以隔离、消除等方式对错误进行定位
2.确定错误产生的原因
3.提出纠正错误的解决办法
4.对程序错误予以改正,重新测试

3.debug和Release的介绍

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用

所以我们说调试就是在Debug版本的环境中,找代码中潜伏的问题的一个过程。
那编译器进行了哪些优化呢?
请看如下代码:

#include 
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<=12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;

这串代码在32位平台下进行编译
如果是 debug 模式去编译,程序的结果是死循环。
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第3张图片

如果是 release 模式去编译,程序没有死循环。
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第4张图片

那他们之间有什么区别呢? 就是因为优化导致的。

三.Windows环境调试介绍.

主要介绍Windous环境在vs编译器下的调试.

1.调试环境的准备

在环境中选择Debug选项,才能使代码正常调试.

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第5张图片

2.学会使用快捷键

在菜单栏处调试选项下对应的调试方式,但每次调试都去找对应的方式有点浪费时间.最好记忆一下对应的快捷键.

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第6张图片

最常用的几个快捷键:
F5
启动调试,经常用来直接跳到下一个断点处。
F9
创建断点和取消断点
断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最
长用的)。
CTRL + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。]
想要了解更多的调试快捷键可以看这里->更多的调试快捷键

3.调试的时候查看程序当前信息

一些常用的调试查看程序当前信息的操作 得在调试过程中才能查看

①查看临时变量的值

在调试开始后,查看变量的值
调试->窗口->监视->任意一个监视窗口->在弹出的监视窗口里 查看想查看的变量值
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第7张图片

②查看内存信息

在调试开始之后,用于观察内存信息。
调试->窗口->内存->任意选一个内存窗口->在地址栏输入对应变量的地址查看该地址指向的位置上的变量内存信息
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第8张图片

③查看调用堆栈

通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置
调试->窗口->调用堆栈 (注意调试的时候要按f11逐语句进入函数,可以查看当前在哪个函数堆栈)

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第9张图片

④查看汇编信息

查看代码对应的的汇编代码
在调试开始之后,有两种方式转到汇编:

(1)第一种方式:右击鼠标,选择【转到反汇编】:

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第10张图片

(2)第二种方式:调试->窗口->反汇编.

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第11张图片

⑤查看寄存器信息

可以查看当前运行环境的寄存器的使用信息
调试->窗口->寄存器

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第12张图片

四.多多动手.尝试调试.才会有进步!

一定要熟练掌握调试技巧。
初学者可能80%的时间在写代码,20%的时间在调试。但是一个程序员可能20%的时间在写
程序,但是80%的时间在调试。
我们所讲的都是一些简单的调试。
以后可能会出现很复杂调试场景:多线程程序的调试等。
多多使用快捷键,提升效率。

五.如何写出易于调试的代码.

多看,多学,多动手写代码,不积跬步无以至千里,不积小流无以成江河!!!

1.优秀的代码

  1. 代码运行正常
  2. bug很少
  3. 效率高
  4. 可读性高
  5. 可维护性高
  6. 注释清
  7. 文档齐全

2.常见的coding技巧

  1. 使用assert
  2. 尽量使用const
  3. 养成良好的编码风格
  4. 添加必要的注释
  5. 避免编码的陷阱。

3.模拟实现库函数strcpy(字符串拷贝函数)

/***
*char *strcpy(dst, src) - copy one string over another
*
*Purpose:
* Copies the string src into the spot specified by
* dest; assumes enough room.
*
*Entry:
* char * dst - string over which "src" is to be copied
* const char * src - string to be copied over "dst"
*
*Exit:
* The address of "dst"
*
*Exceptions:
*******************************************************************************/
#include
#include
char* strcpy(char* dst, const char* src)
{
	char* cp = dst;
	assert(dst && src);
	while (*cp++ = *src++)
		; /* Copy src over dst */
	return(dst);
}
int main()
{
	char arr[] = "hello";
	char str[] = "Hello";
	
	printf("%s", strcpy(arr, str));
	return 0;
}

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第13张图片

上面是库函数strcpy字符串拷贝函数的模拟实现 涉及了 const 关键字 和assert断言函数

4.assert断言函数

assert是一个库函数,使用它对应要包含头文件#include
它的作用是:当后面条件为真时,程序可以继续运行,当为假时,程序在此处停住不能继续执行并弹出断言提示信息便于确定是那一个地方断言失败停止了.
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第14张图片
当传过去的参数是一个NULL时,因为要拷贝字符串,这个字符串肯定不能是NULL,此时设置相应的断言函数,以防传过去是NULL导致程序崩溃错误.此时断言会直接报错断言失败.此时可以精确找到程序断言失败的位置将其修改,而需要一步步调试找错

5.const 关键字介绍

const关键字修饰变量表示该变量具有常属性,是一个常变量,变量里的值在创建并初始化后就不能再更改了.具有只读属性.有了const修饰使得这个变量更安全,里面的数不能被任意修改.

先看一段代码,看看这串代码对应的语句是否都是能正常运行的

 #include 
//代码1
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test2()
{
//代码2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
int main()
{
//测试无cosnt的
test1();
//测试const放在*的左边
test2();
//测试const放在*的右边
test3();
return 0;
}

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第15张图片

程序运行有两处地方出错,可以看出const在修饰指针变量又有所不同.
const修饰指针变量的时候:

  1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改 变。但是指针变量本身的内容可变。
  2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指 针指向的内容,可以通过指针改变。

六.编译常见的错误

1.编译型错误

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第16张图片

2.链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误
还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第17张图片

3.运行时出错

程序一般可以运行,但是运行可能导致程序崩溃(栈溢出),或者运行最后得到的结果不是自己想要的.
需要借助调试,逐步定位问题。最难搞

七.学以致用,调试训练

1.分析代码,找出问题

#include
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

这串代码在上面说到在32位平台,debug选项下执行程序会造成死循环
而正常肉眼看的情况,这串代码会直接报错越界访问,但此时确可以运行.
这串代码单凭肉眼是不可能看出执行过程的.此时我们要通过调试一步步找原因

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第18张图片

通过调试发现,i循环前十次,将数组里十个元素都赋值为了0,但i此时仍然小于12会继续循环,此时将arr[10],arr[11],arr[12]都赋值为了0,但这三个变量都是属于数组外的内容,本来应该是越界访问出错,
但仔细观看arr[12]和i的地址发现,它们竟然完全相同,这说明i和arr[12]用的同一个地址,而地址是独一无二的,说明它们是同一块空间,将此时arr[12]赋值为0,同时i也变为了0,而经过i++后i变为1 此时仍然小于12,继续将arr[1]赋值为0,重复前面的操作最后到arr[12]仍然将自身改为了0,而因为这个循环一直没有中止,此时程序无法停止报错,从而造成了死循环打印hehe!!
而为什么会发生这种情况呢?

2.对代码出现的问题图解

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第19张图片

注意:在不同编译器不同环境下,创建的变量间的间隔是不同的,

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第20张图片

八.总结

很多情况下,我们无法通过肉眼看出程序中存在的bug,调试就显得尤为重要
它像程序员手里的一把利剑,学会使用它,斩尽一切干扰程序正常运行的臭虫
使用它的秘籍就是:做一个有心人,积累排错经验。

还在肉眼找bug??赶紧进来!!!程序员一定要学的调试技巧._第21张图片

写文不易,给一个一键三连,支持下叭~~~

你可能感兴趣的:(#,C语言初阶(从0基础到入门),c语言(从0到1学习),bug,c语言)