C语言 实用调试技巧(Visual Studio编辑器)

一、什么是bug?

程序错误(bug),是程序设计中的术语。是指在软件运行过程中,因为程序本身有错误而造成的功能不正常、死机、数据丢失等现象。

1947年,人们发现了第一个电脑上的bug。
当时电脑出现问题,整个团队都搞不清楚为什么电脑不能正常工作了。经过大家的深度挖掘,发现原来是一只飞蛾意外飞入了电脑内部而引发的故障。团队很快排除错误,在日志本中记录下这一事件。之后,人们渐渐开始使用“Bug”(愿意为“虫子”)来称呼计算机中的错误。


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

每一位优秀的程序员都是一名出色的侦探。
每一次调试都是尝试破案的过程。

调试是什么?


拒绝迷信式调试!!!

调试(Debugging / Debug),是发现和减少计算机程序或者电子仪器设备中程序错误的一个过程。

调试的基本步骤:

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

三、Debug 和 Release 的介绍

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

二者的区别很大,体现在代码运行后的目录文件下。

#include <stdio.h>
int main() {
	char* p = "hello world.";
	printf("%s\n", p);
	return 0;
}

上述代码在Debug环境中的结果展示:
在这里插入图片描述

上述代码在Release环境中的结果展示:


四、windows 环境调试介绍

1.调试环境准备

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


2. 学会快捷键

常用的几个快捷键:

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

F9:创建断点和取消断点,可以在程序中任意位置设置断点。

F10:逐过程调试,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。

F11:逐语句调试,每次都执行一条语句,如果遇到函数,可以使我们进入函数内部查看。(这是最常用的)

CTRL + F5:直接开始执行,跳过所有断点,不调试。


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

查看临时变量的值
在调试开始之后,用于观察变量的值。
可以在调试→窗口→监视→监视1中打开。


查看内存信息
在调试开始之后,用于观察内存信息
在调试→窗口→内存→内存1 打开


查看调用堆栈
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。和栈一样,先进后出。
在调试→窗口→调用堆栈 中打开。


查看汇编信息
开始调试之后,有两种方法转到汇编:
(1)第一种方式:右击鼠标,选择【转到反汇编】;
(2)第二种方式:在调试→窗口→反汇编;


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

4.多多动手,尝试调试,才能有进步。

  • 要掌握调试技巧。
  • 初学者可能80%时间在写代码,20%时间在调试。但是一个程序员可能20%时间在写代码,80%时间在调试。
  • 多多使用快捷键,提升效率。

五、一些调试的实例,以及调试步骤

实例一

实现代码:求1! + 2! + 3! + … + n!

int main() {
	int i = 0;
	int n = 0;
	int sum = 0;
	int ret = 1;
	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,期待输出9,但是结果是15。

为什么?这个时候就是代码有问题,试着运用调试来解决。

调试步骤:

1. 查看代码,给可能出现问题的地方打上断点

2. F5进入调试环境,并且调出监视窗口

3. 按F11,逐步查看代码,要心中有数知道该段代码执行结果,然后与监视窗口对比
我们发现 ret 的值,与预期不一样,3!应该是 6 ,而不是 12。

4. 再进行一次调试,重点查看 ret 的值
发现 ret 计算完还是上次循环的数据,没重置,所以我们加上代码重置试一下。

5. 修改代码,重新运行
OK~


实例二

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

这段代码,很明显是数组越界访问了,但是为什么编译器没有报错,还无限循环输出 hehe 呢?
让我们调试查看一下。

调试到 i=10 还没有报错?

调试到 i=12 继续执行,i=0 了?

奇怪,这时候我们查看一下 arr[12] 的地址
发现 arr[12] 的地址和 i 的地址是一样的。

原来是这样!
数组循环到 i=12 的时候,发现数组 arr[12] 和 i 的地址一样。又因为循环体中 arr[i] = 0 这个代码,把 arr[12] 的数据改为 0,导致 i 的数据也改为 0,回去循环判断又处理。
这才是为什么越界还一直死循环!

然后我们把循环判断条件改小试一下,程序会正常判断到数组越界了。

栈内存·扩展知识:

  1. i 和 arr 是局部变量,局部变量是放在栈区上的。
  2. 栈区的使用习惯是:先使用高地址空间,再使用地址在空间。
  3. 数组随着下标的增长,地址是由低到高变化的。

    ps.可以先定义数组,再定义变量 i 来避免该问题。
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值