C语言学习记录—调试技巧

第一节:调试是什么?

1. 调试

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

2. 调试的基本步骤

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

3. Debug和Release的介绍

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

第二节:调试介绍

1. 调试环境准备

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

2. 常用快捷键

F5
启动调试,经常用来直接跳到下一个断点处。
F9
创建断点和取消断点
断点 的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑 进入函数内部 (这是最
长用的)。
CTRL + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	//赋值
	//for (i = 0; i < 10; i++)
	//{
	//	arr[i] = i;
	//}
	//输入
	for (i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}
	//假设经过分析,上方for循环没有问题,那么要用F9设置断点直接调试下方for循环
	//断点:程序遇到断点就会停下来不会在往后执行,这样就可以一步一步调试
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}

	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

3. 查看临时变量的值

4. 查看内存信息

5. 查看调用堆栈

6. 查看反汇编

F10打开调试有,在编辑窗口点击鼠标右键,选择反汇编

7. 查看寄存器

第三节:调试实例

实例一

通过上图可以看到,在j=3求3的阶乘时,第一次i * ret应该是1*1,但是第一次进入内层循环的时候ret就已经等于2了。这是因为上一次求完2的阶乘留下的。

通过调试可以清晰的看到循环中每个变量的变化,由此找到了问题。所以应该在内层循环开始前,在增加一条语句ret=1,这样每次重置ret的结果,防止上一次计算的阶乘结果被带到下一次阶乘的计算中。

实例二

从上图可以看到,i和arr[12]总是同时变化,到修改arr[12]时,i也被修改了,所以造成了死循环

第四节:如何写出好代码

1. 模拟实现库函数:strcpy

首先可以在MSDN中看到strcpy的用法char* strcpy(char* strDestination, const char* strSource);

int main()
{
	char arr1[20] = "XXXXXXXXXXXX";//验证是否拷贝了\0,如果拷贝了\0,那么倒数第三个X会被覆盖
	char arr2[] = "hello bit";
    strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

通过上图可以看到strcpy库函数在拷贝时,会拷贝\0。

版本一
void my_strcpy(char* dest, char* src)
{
	while (*src != '\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
	//上方代码没有将\0拷贝,但*src已经指向了\0
	*dest = *src;//最后一步将\0也拷贝
}

优化版本一
//优化版本1
void my_strcpy(char* dest, char* src)
{
	while (*src != '\0')
	{
		*dest++ = *src++;//优化1:后置++。因为先解引用才++
	}
	*dest = *src;
}

优化版本二
//优化版本2
#include <assert.h>
//为了防止(下方代码)有人把源字符串和destination写反,在源字符串指针前const,把源字符串的指针修改为常对象,让它不能修改
void my_strcpy(char* dest, const char* src)
{
	//防止传了空指针
	//断言
	assert(src != NULL);//这个表达式判断为假(即src为空指针)时,assert就报错
	assert(dest != NULL);
	
	//这样,如果像下面写反,编译器会报错
	//while (*src++ = *dest++)

    //1. 首先*src指向字符串的h,并放到dest第一个X位置上
	//2. 这是一个赋值表达式,h赋值过去的是时候,这个表达是的结果就是h的ASCII码值
	//3. 这个ASCII码值不为0,不是假,所以进入循环
	//4. 然后因为后置++,src和dest往后移动一位,继续重复上述123步骤
	//因为拷贝的每一个字符的ASCII码值都不为0,所以都进入循环,
	//遇到\0,*src依然赋值给了*dest。此时表达式结果0,循环停止
	while (*dest++ = *src++)
	{
		;
	}
	//*dest = *src;//上面也拷贝,这里也拷贝可以优化
}

优化版本三

为什么库函数strcpy返回char*,是为了实现链式调用,返回类型写成char*,那么这个函数的返回值就可以作为其他函数的参数。

char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;//一开始保存目标地址,方便最后返回
	assert(src != NULL);
	assert(dest != NULL);

	while (*dest++ = *src++)
	{
		;
	}
	//return dest;//err,原因strcpy返回的是目标空间的起始地址,但是目标地址在循环过程中一直在++
	return ret;
}
int main()
{
	char arr1[20] = "XXXXXXXXXXXX";
	char arr2[] = "hello bit";

	//模拟实现strcpy
	my_strcpy(arr1, arr2);

	//因为my_strcpy返回的是目标地址,所以可以直接打印
	//printf("%s\n", my_strcpy(arr1, arr2));
	printf("%s\n", arr1);//类似上方,arr1也是字符串的起始地址
	return 0;
}

2. const的作用

1. const修饰变量

如果const修饰一个整形变量,那么无法重新赋值修改它。但是获取该变量的地址,通过指针可以修改该变量。

int main()
{
	const int num = 10;
	num = 20;//不能改
	int* p = &num;//可以改
	*p = 20;//可以修改

	return 0;
}

2. const修饰指针变量
int main()
{
	//为了防止修改,在指针变量前加const
	//const修饰指针变量
	//1. const放在*的左边(下方两种写法都一样)
	//const int* p;//一般使用这种,const修饰的是*p
	//int const* p;
	//意思是:p指向的对象不能通过p来改变,但是p变量本身的值可以改变(也就是指针p里面存的地址可以改变,即可以让它指向另外一个对象)
    const int num = 10;
	const int* p = &num;
	int n = 100;

	*p = 20;//err
	p = &n;//ok
	


	//2. const放在*的右边
	//意思是:p指向的对象可以通过p来改变,但是不能修改p变量本身的值(即指针变量p里面存放的地址不能改变,即不能让它指向其他对象)
	const int num = 10;
    int* const p = &num;
	*p = 0;//ok
	int n = 100;
	p = &n;//err


	//3. 如果*两边都加const,那么p指向的对象不能修改,p本身也不能修改
	
	printf("%d\n", num);
	return 0;
}

第五节:编程常见错误

1.  编译型错误

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。

2. 链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。

3. 运行时错误

借助调试,逐步定位问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值