C语音初阶————调试实用技巧2

总结一下这几天学习的内容,将调试章节的剩下部分全部学习完,学习内容主要分为以下五个部分:

**以下调试主要用VS2022做示例**

(1)调试的时候查看程序当前信息

按下F5(F5+Fn)进入调试(一些时候可能需要设置断点才能进入调试),在调试环境下,选择【调试】选项卡→【窗口】选项进行以下菜单的选择

1.查看临时变量的值:便于观察变量的值

查看临时变量的值可以通过的【自动窗口】、【局部变量】、【监视】(监视窗口可以有多个同时监视不同变量)。以下面代码为例:

cs 复制代码
int main()
{
	int arr[10] = { 0 };
	int i;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
		printf("%d\n", arr[i]);
	}
	return 0;
}

【自动窗口】

【局部变量】

【监视】

2.查看内存信息:可设置行的列数(一列一个字节,内存用16进制显示)

通过【内存】选项进行查看(内存窗口可以有多个,方便多个变量同时查看)

3.查看调用堆栈:清晰的反应函数调用关系和该函数所处位置

数据结构中的站:压栈、出栈。可以通过【并行堆栈】和【调用堆栈】查看,以以下代码为例:

cs 复制代码
int add(int n, int m)
{
	return n + m;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = add(a, b);
	printf("%d\n", c);
	return 0;
}

【并行堆栈】

【调用堆栈】

4.查看汇编信息:可切换到汇编代码

通过【反汇编】进行查看

5.查看寄存器信息:当前运行环境的寄存器的使用信息

【寄存器】

(2)多多动手。尝试调试,才能有进步

①要熟练掌握调试技巧

②对于大多数初学者是80%的时间写代码,20%的时间在调试

但是程序员实际上只有20%的时间写代码,80%的时间在调试。

③从简单的调试入手,慢慢尝试更为复杂的调试场景,例如多线程调试。

④多使用快捷键,提升效率。

(3)调试实例

1.n的阶乘的和

cs 复制代码
int main()
{
	int n = 0;
	int sum = 0;
	int ret = 1;
	int i, j;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= i; j++)
		{
			ret *= i;
		}
	}
	return 0;
}

以上代码的ret为重置所以会导致代码最终结果会错误,可以通过调试找到错误并进行修改。

2.数组的越界访问(代码在X80环境下运行)

cs 复制代码
int main()
{
	int i = 0;
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

该代码越界访问,为什么不报错?且为什么结果为死循环?

原因:1.栈区内存的使用习惯是先使用高地址的空间,再使用低地址处的空间。

2.数组随着下标的增长地址是由低到高变化的。

3.如果i和arr之间由适当的arr空间,利用数组的越界操作就可能会覆盖到i,就可能会导致死循环出现。

* *这一知识点在《C语音的缺陷》中有讲过,在nice公司的笔试题也曾经出现过* *

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

1.优秀的代码:

①代码运行正常

②bug很少

③效率高

④可读性高

⑤可维护性高

⑥注释清晰

⑦文档齐全

2.常见的coding(编程)技巧:

①使用assert(断言,使用时需包含assert.h头文件)

②尽量使用const(修饰常量)

③养成良好的编程风格

④添加必要的注释

⑤避免编程陷阱

3.示范

①模拟写出库函数strcpy函数

首先了解strcpy函数,strcpy 是一个标准字符串处理函数,用于将一个字符串复制到另一个字符数组中。使用该库函数时需要包含头文件string.h,使用模版 char* strcpy( char* strDestination , cnost char* strSource ),其中strDestinetion代表目标空间,strSource代表源数据。

使用示例代码:

cs 复制代码
#include<string.h>
int main()
{
	char arr1[] = { "XXXXXXXXXXXXXXXXXXXXX" };
	char arr2[] = { "Holle Word!" };
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

在这个示例中可以看出strcpy函数在拷贝字符的时候,会把源字符串中的'\0 '也拷贝

了解strcpy函数后模拟自己写一个strcpy函数:一共分为两个版本,一个是以初学者写出较为简单的代码,而另一个更符合以上所说的"优秀的代码"也是更接近库函数实际使用的代码。

cs 复制代码
//版本一:
void my_strcpy(char* dest, char* str)
{
	while (*str != '\0')
	{
		*dest++ = *str++;
	}
	*dest = *str;
}
//版本一不符合优秀代码的要求
//版本二:
#include<assert.h>
char* my_strcpy1(char* dest, const  char* str)
//在第二个源数据参数前添加const,以防代码中两个参数的赋值的错误
{
	char* ret = dest;
	//使用断言函数assert控制使用的两个字符串的可操作性(不能对空指针进行操作)
	assert(str != NULL);
	assert(dest != NULL);
	//将操作语句直接写到循环的判断语句中,使得代码更为简洁
	while (*dest++ = *str++)
	{
		;
	}
	return ret;
}
//为什么返回值写char*呢?
//是为了实现链式访问。
//strcpy函数返回的是目标空间的起始地址
int main()
{
	char arr1[] = { "XXXXXXXXXXXXXXXXXXXXX" };
	char arr2[] = { "Holle World!" };
	my_strcpy(arr1, arr2);
	printf("%s\n", arr1);
	printf("%s\n", my_strcpy1(arr1, arr2));
	return 0;
}

在版本二中更好的解释了断言函数assert和const修饰变量的含义,以及使用char*的原因:

①使用断言函数assert控制使用的两个字符串的可操作性(不能对空指针进行操作)

②在第二个源数据参数前添加const,以防代码中两个参数的赋值的错误。

③为什么返回值写char*呢?

是为了实现链式访问。strcpy函数返回的是目标空间的起始地址。

const修饰变量指针:

①const放在*的左边

意思:p指向的对象不能通过p改变,但是p变量本身的值可以改变。

②const放在*的右边

意思:t指向的对象可以通过t改变,但是t变量本身的值不能改变。

cs 复制代码
int main()
{
	int num = 20;
	int n = 10;
	//1.const放在*的左边
	const int* p = &num;
	*p = 10; //false
	p = &n;//true
	//意思:p指向的对象不能通过p改变,但是p变量本身的值可以改变
	//2.const放在*的右边
	int* const t = &num;
	*t = 10; //true
	t = &n; //false
	//意思:t指向的对象可以通过t改变,但是t变量本身的值不能改变
	return 0;
}

练习:模拟实现一个strlen函数(求字符串长度)

cs 复制代码
#include<assert.h>
int my_strlen(const char* str)//保证参数不能被修改
{
	int count = 0;
	assert(str);//保证指针不为空指针
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	char arr[] = { "Holle World!" };
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

(5)编程常见的错误

先了解一下编程的过程:test.c文件→编译→链接→可执行程序

1.编译型错误(也就是语法错误,比较简单)在编译步骤中出现。

直接看错误提示信息(双击),解决问题。或者凭借经验就可以解决。

cs 复制代码
int mian()
{
	int a=0
	//常见忘记打分号
	return 0;
}

2.链接型错误(标识符不存在或者拼写错误,出现在链接期间)

查看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。查找时可使用Ctrl+F进行查找,如果找不到可能的原因有两个:一是不存在;二是写错了。

cs 复制代码
int main()
{
	int a = 10;
	int b = 20;
	int c=add(a, b);
	//add函数未定义(不存在)
	printf("%d\n", c);
	return 0;
}

3.运行时错误(逻辑错误,最复杂)

借助调试,逐步定位问题(以上文的调试实例n的阶乘的和为例)。

相关推荐
沛沛老爹2 小时前
从Web到AI:行业专属Agent Skills生态系统技术演进实战
java·开发语言·前端·vue.js·人工智能·rag·企业转型
程农2 小时前
基于Java的报名系统
java·开发语言
yugi9878383 小时前
基于字典缩放的属性散射中心参数提取MATLAB仿真程序
开发语言·matlab
小白学大数据3 小时前
绕过拼多多 App 反抓包机制的综合逆向解决方案
开发语言·爬虫·python·自动化
使者大牙3 小时前
【单点知识】 Python装饰器介绍
开发语言·数据库·python
带土13 小时前
2. C++ private、protected、public
开发语言·c++
我不是8神3 小时前
字节跳动 Eino 框架(Golang+AI)知识点全面总结
开发语言·人工智能·golang
古城小栈3 小时前
Rust复合类型 四大军阀:数、元、切、串
开发语言·后端·rust
kong79069283 小时前
Python核心语法-Python自定义模块、Python包
开发语言·python·python核心语法