调试技巧总结

目录

1.调试是什么?

2. 调试的基本步骤

3.Debug和Release的介绍 

4. 调试实例

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

4.2 分析这段代码

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

5.1 优秀的代码:

5.2 示范

6.补充知识

6.1 const讲解 

7.编译常见的错误


1.调试是什么?

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

2. 调试的基本步骤

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

3.Debug和Release的介绍 

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

4. 调试实例

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

#include <stdio.h>

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = 1;
	int sum = 0;
	for (n = 1; n <= 3; n++)
	{
		int i = 0;
		for (i = 1; i <= n; i++)
		{
			ret *= i;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

1!+2!+3!= 9 但上述代码算出的结果是15.这说明我们的代码出了问题,开始进行调试。 

当我们进行计算3的阶乘的时候,ret的值还是2,这种代码的写法是从1一直乘到所求的那个数上去,所以我们要将每次进入循环的ret的值设为1。

正确代码

int main()
{
	int n = 0;
	scanf("%d", &n);

	int ret = 1;
	int i = 0;
	int sum = 0;
	for (i = 1; i <= n; i++)
	{
		ret *= i;
		sum += ret;
	}
	printf("%d", sum);
	return 0;
}

4.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;
}

首先,我们很容易就看出数组越界访问了,但在组代码的结果在不同环境下的结果是不一样的。在vs中,它是会死循环的。

解释:因为内存中栈区的使用习惯是先使用高地址空间,再使用低地址空间,于是乎操作系统就在栈中就先分配给了局部变量i一块空间,随后又给数组分配了一块空间,而内存中数组是连续存放的,它随着下标的增长地址由低到高。而我们在数组中放了元素0之后,在vs中数组越界了,vs这个编译器依然会给数组越界后的内存放上数字0,但是vs中变量和数组在内存中的位置相差两个字节,当程序到arr[12]的时候,实际上是访问到了变量i的空间,此时将arr[12]中放上数据0,实际上就是将变量 i 中的值修改为了0,因为此时两者指向的是同一块内存空间。

再次注意,不同的编译环境中,结果可能是不同的。在vc6.0中 i 和 arr 在内存中是没有空隙的。

而在gcc中,两者之间相差一个字节,在vs中则相差两个字节。

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

5.1 优秀的代码:

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


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

5.2 示范

模拟实现strcpy,博主将展示不断优化的代码

博主将展示不断优化的代码,满分为10分

方法一,5分 

void my_strcpy(char* dest, char* src)//传进来两个指针
{
	while (*src != '\0')
	{
		*dest = *src;
		src++;
		dest++;
	}
	*dest = *src;   //这里最后将src指向的'\0'赋给dest指向的区域
}

int main()
{
	char arr1[] = "hello haha";
	char arr2[20] = "xxxxxxxxxxxxxx";

	my_strcpy(arr2,arr1);
	printf("%s\n", arr2);
	return 0;
}

 方法二,6分

void my_strcpy(char* dest, char* src)//传进来两个指针
{
	while (*src != '\0')
	{
		*dest++ = *src++;
		
	}
	*dest = *src;   //这里最后将src指向的'\0'赋给dest指向的区域
}

int main()
{
	char arr1[] = "hello haha";
	char arr2[20] = "xxxxxxxxxxxxxx";

	my_strcpy(arr2,arr1);
	printf("%s\n", arr2);
	return 0;
}

 方法三,7分

void my_strcpy(char* dest, char* src)//传进来两个指针
{
	while (*dest++ = *src++)
	{
		;
	}  
}

int main()
{
	char arr1[] = "hello haha";
	char arr2[20] = "xxxxxxxxxxxxxx";

	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

 解释一下,这句语句,其实就是先将*src赋给*dest,然后src++,dest++,当src指向‘\0’的时候,先将'\0'赋给dest,但是此时*dest++ = *src++ 这个表达式的值为0,循环就结束了。

方法四,8分 

#include <stdio.h>
#include <assert.h>

void my_strcpy(char* dest, char* src)
{
	assret(src != NULL);
	assret(dest != NULL);
	while (*dest++ = *src++)
	{
		;
	}  
}

int main()
{
	char arr1[] = "hello haha";
	char arr2[20] = "xxxxxxxxxxxxxx";
	int* p = NULL;

	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

这里,用到了一个函数assert(断言),这样做的原因是我们在传参中,有时候会误将空指针给传进去,这时候就会出现读写异常,而使用assert之后呢,可以快速的帮我们定位到问题的所在,因为当你传了空指针时,连编译都不让你编过去。

方法5, 9分

void my_strcpy(char* dest, char* src)
{
	assret(src && dest);
	
	while (*dest++ = *src++)
	{
		;
	}
}

int main()
{
	char arr1[] = "hello haha";
	char arr2[20] = "xxxxxxxxxxxxxx";
	//int* p = NULL;

	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

这里就是将两个assert组合了起来。

方法6,满分

不知道你有没有注意到strcpy返回的是一个字符型的指针,还有src前面有一个const修饰。我们要模拟就要做到最好。 my_strcpy函数设计返回值类型是为了实现函数的链式访问。

#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* dest, const char* src)
{
	assert(src && dest);
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char arr1[] = "hello haha";
	char arr2[20] = "xxxxxxxxxxxxxx";
	//int* p = NULL;

	
	printf("%s\n", my_strcpy(arr2, arr1));
	return 0;
}

6.补充知识

6.1 const讲解 

int main()
{
	//const int num = 10;  //这里编译不同的,是因为表达式必须是可修改的值
	//num = 20;            //这里的num此时已经是常变量了,所以不可被修改。
	
	int num = 20;
	int n = 0;
	const int* p1 = &num;
	p1 = &n;

	int* const p2 = &num;
	*p2 = 10;

	return 0;
}

const 可以修饰指针
const 放在*的左边(const int* p1;)
const修饰的是*p1,表示p1指向的对象不能通过p1来改变,但是p1变量中的地址是可以改变的
const 放在*的右边(int* const p2;)
const 修饰的是p2,表示p2的内容不能被改变,但是p2指向的的对象是可以通过p来改变的

7.编译常见的错误


编译型错误就是语法错误
链接型错误
运行时错误 - 借助调试解决的错误

#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;
	
	int c = Add(a, b);
	
	printf("%d\n", c);

	return 0;
}

void* p;

void test(void)
{
	printf("hehe\n");
}

int main()
{

	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_63562631/article/details/126227656