『C』调试技巧

什么是bug?

bug一次的原意是“昆虫”或虫子;而在电脑系统或程序中隐藏着一些违背发现的缺陷或问题,人们也叫它“bug”。
“bug”的创始人格蕾丝·赫柏(Grace Murray Hopper),是一位为美国海军工作的电脑专家,也是最早将人类语言融入到电脑程序的人之一。而代表电脑程序出错的“bug” 这名字,正是由赫柏所取的。1947年9月9日,赫柏对Harvard Mark II设置好17000个继电器进行编程后,技术人员正在进行整机运行时,它突然停止了工作。于是他们爬上去找原因,发现这台巨大的计算机内部一组继电器的触点之间有一只飞蛾,这显然是由于飞蛾受光和热的吸引,飞到了触点上,然后被高电压击死。所以在报告中,赫柏用胶条贴上飞蛾,并把“bug”来表示“一个在电脑程序里的错误”,“Bug”这个说法一直沿用到今天。
在这里插入图片描述
第一次发现的导致计算机错误的飞蛾,也是第一个计算机程序错误。

调试是什么?

程序调试是将编写的程序投入实际运行前,用手工或编译程序等方法进行测试,修正语法错误和逻辑错误的过程。这是保证计算机信息系统正确性的必不可少的步骤。编完计算机程序,必须送入计算机中测试。根据测试时所发现的错误,进一步诊断,找出原因和具体的位置进行修正。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
拒绝迷信式调试!!!

调试基本步骤

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

Debug和Release的介绍

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

Debug和Release产生文件大小对比

#include <stdio.h>
#include <stdlib.h>

int main(){

	char str[] = "hello, world!";

	printf("%s\n", str);

	system("pause");
	return 0;
}

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

Debug和Release反汇编对比

在这里插入图片描述
所以说调试就是在Debug版本的环境中,找代码中潜伏的问题的一个过程。
编译器做了哪些优化呢

先来看一段代码

#include <stdio.h>
#include <stdlib.h>

int main(){

	int i = 0;
	int arr[10] = { 0 };

	for (i = 0; i <= 12; ++i){
		arr[i] = 0;
		printf("hello, world!\n");
	}

	system("pause");
	return 0;
}

首先这个代码在Debug模式下是死循环,让我们来看看为什么是死循环?
让我们来调试看看。
在这里插入图片描述
从上面可以看出来,数组先创建,i变量后创建。它们之间间隔48个字节。也就是i和arr[12]指向的是同一块内存空间。所以当for循环执行到i = 12时,会将arr[12]指向空间的内容修改为0,也就是将i的值修改为0。从而导致死循环。
如果是Release模式去编译,程序没有死循环。这又是为什么呢?
在这里插入图片描述
从上面的结果可以看出,i变量先于数组创建,就不会出现Debug模式下出的问题。因此Release模式下不会陷入死循环主要就是由于优化导致的,变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果

Windows环境调试介绍

调试环境的准备

VS2013
在这里插入图片描述
在环境中选择Debug选项,正常调试。

快捷键介绍

在这里插入图片描述

  • F5
    启动调试,经常用来直接跳到下一个断点出。
  • F10
    逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
  • F11
    逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)。
  • CTRL + F5
    开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。

调试时查看程序当前信息

查看临时变量的值

在这里插入图片描述

查看内存信息

在这里插入图片描述

查看调用堆栈

在这里插入图片描述

查看汇编信息

方法一

右键函数名,转到反汇编。
在这里插入图片描述

方法二

在这里插入图片描述

查看寄存器信息

在这里插入图片描述

调试实例

实例一

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){

	int i, j, n;
	int sum = 0;
	int ret = 1;

	printf("Please input the n: \n");
	scanf("%d", &n);

	for (i = 1; i <= n; ++i){
		for (j = 1; j <= i; ++j){
			ret *= j;
		}
		sum += ret;
	}

	printf("%d! = %d\n", n, sum);

	system("pause");
	return 0;
}

运行结果

在这里插入图片描述
3的阶乘加2的阶乘加1的阶乘为15,结果明显不对,开始调试
在这里插入图片描述
将断点打在for循环入口处。
F5启动调试
输入n的值3。
打开监视窗口,监视变量i、j、ret和sum的值
在这里插入图片描述
F11逐语句进行调试,观察监视变量的值。
在这里插入图片描述
观察监视窗口过程中,发现ret的值变化不正常,根据代码可知,ret应该是i的阶乘。但是当i为3时,ret竟然是12,这明显不正常,我们在取看源代码就会发现,ret一直在累乘,并未在计算完i的阶乘后将其值设为1。故而出错。
在这里插入图片描述
修改之后,结果如下:
在这里插入图片描述

实例二

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){

	int i = 0;
	int arr[10] = { 0 };

	for (i = 0; i <= 12; ++i){
		arr[i] = 0;
		printf("hello, world!\n");
	}

	system("pause");
	return 0;
}

运行结果

在这里插入图片描述
结果是死循环,这是为什么呢?调试来看看。首先既然是死循环,我们就将断点打在循环入口处。
在这里插入图片描述
F5开始调试
打开监视窗口,既然是死循环,我们就来监视一下i的值
在这里插入图片描述
从监视窗口可以看出,i在增加到12后,再加1就变成了0,这是为什么呢?
我们在监视窗口中添加&i和&arr[i]来观察一下。
在这里插入图片描述
观察监视窗口可以看出,i变量的地址和arr[12]是同一个地址,也就是程序对arr[12]的值进行修改的时候,i的值也会得到相应的修改。即i的值被赋0。

常见的coding技巧

  • 使用assert
  • 尽量使用const
  • 养成良好的编码风格
  • 添加必要的注释
  • 避免编码的陷阱。

coding常见的错误

  • 编译型错误
    直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单
  • 链接型错误
    看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或拼写错误
  • 运行时错误
    借助调试,逐步定位问题。最难搞
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值