C语言——实用调试技巧——第2篇——(第23篇)

坚持就是胜利

文章目录


一、实例

c 复制代码
#include <stdio.h>

int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

这段程序非常依赖当前所在的编译环境的,编译环境不同,出现的效果也是不同的。





类似的题目:

上一篇文章介绍了,在Debug版本下,上述代码会死循环。

但是,在Release版本下,上述代码不会死循环。

原因如下:

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

1、优秀的代码

1、代码运行正常

2、BUG很少

3、效率很高

4、可读性高

5、可维护性高

6、注释清晰

7、文档齐全

常见的coding技巧:

1、使用 assert

2、尽量使用 const

3、养成良好的编码风格

4、添加必要的注释

5、避免编码的陷阱

2、示范

(1)模拟 strcpy 函数

char * strcpy ( char * destination, const char * source );

Copies the C string pointed by source into the array pointed by destination,

including the terminating null character (and stopping at that point).

(第二句英文:包含结束字符 '\0')

将'h','e','l','l','o','\0' 传给arr2数组

方法一:
c 复制代码
#include <stdio.h>
#include <string.h>
 
int main()
{
	char arr1[] = "hello";  //将'h','e','l','l','o','\0' 传给arr2[]数组
	char arr2[20] = { 0 };    //别忘了  结束标志: '\0'
	strcpy(arr2, arr1);
	printf("%s\n", arr2);

	return 0;
}
方法二:
c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };

	printf("%s\n", strcpy(arr2, arr1));

	return 0;
}
方法三:有弊端
c 复制代码
#include <stdio.h>

void my_strcpy(char* dest, char* src)
{
	while (*src != '\0')  //或者简洁点写成: while(*src)
	{
		*dest = *src;
		dest++;
		src++;
	}
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };
	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);

	return 0;
}
c 复制代码
#include <stdio.h>

void my_strcpy(char* dest, char* src)
{

	while (*dest = *src)
	{
		dest++;
		src++;
	}
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };

	char* ps = NULL;

	my_strcpy(arr2, arr1);

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


	return 0;
}
c 复制代码
#include <stdio.h>

void my_strcpy(char* dest, char* src)
{

	while (*dest++ = *src++)  //先执行后置 ++,再 解引用 *
	{
		;    //空语句     //什么都不做
	}
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };

	char* ps = NULL;

	my_strcpy(arr2, arr1);

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


	return 0;
}

虽然可以正常输出,但是遇到 空指针 NULL ,就会出错

c 复制代码
#include <stdio.h>

void my_strcpy(char* dest, char* src)
{
	while (*src != '\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };

	char* ps = NULL;      //此时 ps 是 空指针,空指针 是 不能直接使用的

	my_strcpy(ps, arr1);  //这样子,整个代码是什么都输出不了的,空指针 是 不能直接使用的
	
	printf("%s\n", *(ps));  //什么都输出不了,程序报错


	return 0;
}
方法四:对方法三进行优化
assert 的使用

头文件:#include <assert.h>

assert 的作用:会清晰的告诉你,哪一行的代码出现了错误!

c 复制代码
#include <stdio.h>

#include <assert.h>  //assert 的头文件

void my_strcpy(char* dest, char* src)
{
	//断言 assert
	assert(dest != NULL);   //dest 不允许为 空指针
	assert(src != NULL);    //src  不允许为 空指针


	while (*src != '\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };

	char* ps = NULL;      

	my_strcpy(ps, arr1);  
	
	printf("%s\n", *(ps));  


	return 0;
}
方法五:对方法三、方法四进行优化

从方法一 ~ 方法四,my_strcpy函数的返回值都是 void .

因为并没有 return ,所以都是 void。

然而,根据 strcpy函数的定义: char * strcpy ( char * destination, const char * source );

返回值的类型,应该是:char *

const char* source,要有 const

1、解决char*
问题一:怎么返回 起始地址?
c 复制代码
#include <stdio.h>

char* my_strcpy(char* dest, char* src)
{
	//断言
	assert(dest != NULL);
	assert(src != NULL);

	while (*dest++ = *src++)
	{
		;   
	}

	return dest;  //此时的 dest 已经指向了数组的最后了,返回之后,无法输出想要的字符串
}                 //我们需要的是:目标函数的 起始地址

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };
	
	my_strcpy(arr2, arr1);

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

	return 0;
}
解决办法
c 复制代码
#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* dest, char* src)
{
	char* ret = dest;      //问题得到解决

	//断言
	assert(dest != NULL);
	assert(src != NULL);

	while (*dest++ = *src++)
	{
		;
	}

	return ret;           //就是这么简单
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };
	
	printf("%s\n", my_strcpy(arr2, arr1));

	return 0;
}
2、解决const,因为 const char* source
可能出现的问题
c 复制代码
#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* dest, char* src)
{
	char* ret = dest;      //问题得到解决

	//断言
	assert(dest != NULL);
	assert(src != NULL);

	while (*src++ = *dest++)     //本来应该是:while (*dest++ = *src++),
	{                            //但是写成了:while (*src++ = *dest++)。
		;
	}

	return ret;           //就是这么简单
}

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

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

	return 0;
}
c 复制代码
#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* dest,const char* src)   //添加 const
{
	char* ret = dest;      //问题得到解决

	//断言
	assert(dest != NULL);
	assert(src != NULL);

	while (*src++ = *dest++)     //本来应该是:while (*dest++ = *src++),
	{                            //但是写成了:while (*src++ = *dest++)。
		;
	}

	return ret;           //就是这么简单
}

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

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

	return 0;
}
修饰指针 的作用

结论:

1、const 如果放在 * 的 左边,修饰的是 指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。

2、const 如果放在 * 的 右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

c 复制代码
#include <stdio.h>

int main()
{

	const int num = 100;   //下面的截图中,忘记添加 const 了,应该是 const int num = 100;

	int a = 90;

	const int* ps = &num;

	//*ps = 200;  //不能这样改变

	ps = &a;

	printf("%d\n", *(ps));

	return 0;
}
c 复制代码
#include <stdio.h>

int main()
{
	const int num = 10;

	//int abc = 200;

	int* const ps = &num;

	//ps = &abc;   //错误

	*(ps) = 200;

	printf("%d\n", num);

	return 0;
}
方法六:最终的正确结果
c 复制代码
#include <stdio.h>

#include <assert.h>

char* my_strcpy(char* dest, const char* src)  //const 在 * 的左边
{                                             //保证 指针指向的内容不会发生改变
	char* ret = dest;

	//断言
	assert(dest != NULL);
	assert(src != NULL);

	while (*dest++ = *src++)
	{
		;   //空语句,什么都不做
	}

	return ret;
}

int main()
{
	char arr1[] = "hello";
	char arr2[20] = { 0 };

	char* ps = NULL;

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

(2)模拟 strlen 函数

size_t strlen ( const char * str );

%u 或者 %zd 来打印 无符号整型(unsigned int)。

The length of a C string is determined by the terminating null-character: A C string is as long as the number of characters between the beginning of the string and the terminating null character

(without including the terminating null character itself).

最后一句话:字符串的长度 不包含 结束字符 '\0'。

c 复制代码
#include <stdio.h>
#include <assert.h>

size_t my_strlen(const char* src)
{
	int count = 0;

	//断言
	assert(src != NULL);

	while (*src++)
	{
		count++;
	}
	return count;
}

int main()
{
	char arr1[] = "hello";

	const char* ps = arr1;

	size_t len = my_strlen(ps);

	printf("%zd\n",len);    //%zd  或者  %u  打印 无符号整型

	return 0;
}

三、编程常见的错误

1、编译型错误(语法错误)

在编译期间,产生的错误,都是:语法问题。

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

2、链接型错误

在链接期间,产生的错误。

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。

一般是 标识符名不存在 或者 拼写错误。

3、运行时错误

程序运行起来了,但是结果不是我们想要的,逻辑上出现问题。

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

四、做一个有心人,积累排错经验。

做一个错题本,将 调试的错误 都积累起来!

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

相关推荐
懒大王就是我33 分钟前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
半盏茶香36 分钟前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
小堇不是码农43 分钟前
在VScode中配置C_C++环境
c语言·c++·vscode
小肥象不是小飞象1 小时前
(六千字心得笔记)零基础C语言入门第八课——函数(上)
c语言·开发语言·笔记·1024程序员节
励志成为嵌入式工程师6 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
Peter_chq6 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
hikktn8 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
观音山保我别报错8 小时前
C语言扫雷小游戏
c语言·开发语言·算法
小林熬夜学编程10 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
墨墨祺11 小时前
嵌入式之C语言(基础篇)
c语言·开发语言