0
点赞
收藏
分享

微信扫一扫

C语言系列——第八节-实用调试技巧

实用调试技巧

1. 什么是bug?

在这里插入图片描述
第一次被发现的导致计算机错误的飞蛾,也是第一个计算机程序错误。

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

一名优秀的程序员是一名出色的侦探。

我们是如何写代码的?
在这里插入图片描述
又是如何排查出现的问题的呢?
在这里插入图片描述
拒绝-迷信式调试!!!!

2.1 调试是什么?

2.2 调试的基本步骤

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

2.3 Debug和Release的介绍

代码:

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

上述代码在Debug环境的结果展示:
在这里插入图片描述
上述代码在Release环境的结果展示
在这里插入图片描述
Debug和Release反汇编展示对比:
在这里插入图片描述
所以我们说调试就是在Debug版本的环境中,找代码中潜伏的问题的一个过程。
那编译器进行了哪些优化呢?
请看如下代码:

#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;
}

解释:为什么可能会死循环;因为程序在按着顺序执行时,栈
区的使用习惯是先占用高地址,再使用低地址,如下图

所以i占的地址,可能就是数组的第12个元素所在的位置

在这里插入图片描述

如果是 debug 模式去编译,程序的结果是死循环。
如果是 release 模式去编译,程序没有死循环。
那他们之间有什么区别呢?
就是因为优化导致的。在这里插入图片描述
变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果。

3. Windows环境调试介绍

3.1调试环境的准备

在这里插入图片描述

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

3.2 学会快捷键

在这里插入图片描述
最常使用的几个快捷键:
F5

F9

F10

F11

CTRL + F5

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

3.3.1 查看临时变量的值

在调试开始之后,用于观察变量的值。
在这里插入图片描述

3.3.2 查看内存信息

在调试开始之后,用于观察内存信息在这里插入图片描述

3.3.3 查看调用堆

在这里插入图片描述

通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置

3.3.4 查看汇编信息

在调试开始之后,有两种方式转到汇编:
(1)第一种方式:右击鼠标,选择【转到反汇编】:
在这里插入图片描述
(2)第二种方式:在这里插入图片描述
可以切换到汇编代码。

3.3.5 查看寄存器信息

在这里插入图片描述
可以查看当前运行环境的寄存器的使用信息。

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

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

5. 一些调试的实例

实现代码:求 1!+2!+3! …+ n! ;不考虑溢出。

int main()
{
	int i = 0;
	int sum = 0;//保存最终结果
	int n = 0;
	int ret = 1;//保存n的阶乘
	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;
}
#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;
}

这时候我们如果3,期待输出9,但实际输出的是15。
why?
这里我们就得找我们问题。

5.2 实例二

#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;
}

研究程序死循环的原因。(上述有过程讲解,栈先使用高地址)(与大小端不同)

6. 如何写出好(易于调试)的代码

6.1 优秀的代码

常见的coding技巧

6.2 示范:

/***
*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:
*******************************************************************************/
char * strcpy(char * dst, const char * src) {
        char * cp = dst;
 assert(dst && src);
 
        while( *cp++ = *src++ )
               ;     /* Copy src over dst */
        return( dst );
}
1.对于不需要改变的值,可以使用const
2.对于指针用 assert 检测

注意:

6.3 const的作用

#include <stdio.h>
//代码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;
}

结论:
const修饰指针变量的时候:

注:
介绍《高质量C/C++编程》一书中最后章节试卷中有关 strcpy 模拟实现的题目。
练习:

#include <stdio.h>
int my_strlen(const char* str) {
	int count = 0;
	assert(str != NULL);
	while (*str)//判断字符串是否结束
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	const char* p = "abcdef";
	//测试
	int len = my_strlen(p);
	printf("len = %d\n", len);
	return 0;
}

7. 编程常见的错误

7.1 编译型错误

7.2 链接型错误

7.3 运行时错误

温馨提示:

举报

相关推荐

0 条评论