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号字
红色
绿色
蓝色

相关推荐
Felven5 小时前
A. Helmets in Night Light
c语言
Illusionna.7 小时前
C语言自动进行独立样本 t 检验
c语言·自动化·显著性·统计检验·独立样本t检验·ttest·levene
qq_401700417 小时前
C语言 条件编译宏
c语言·开发语言
逑之7 小时前
C语言笔记5:函数
java·c语言·笔记
无限进步_7 小时前
【C语言&数据结构】对称二叉树:镜像世界的递归探索
c语言·开发语言·数据结构·c++·git·算法·visual studio
松涛和鸣7 小时前
49、智能电源箱项目技术栈解析
服务器·c语言·开发语言·http·html·php
凉、介7 小时前
SylixOS 中的 Unix Socket
服务器·c语言·笔记·学习·嵌入式·sylixos
X***07888 小时前
从底层逻辑到工程实践,深入理解C语言在计算机世界中的核心地位与持久价值
c语言·开发语言
智者知已应修善业8 小时前
【编写函数求表达式的值】2024-4-3
c语言·c++·经验分享·笔记·算法
HABuo8 小时前
【Linux进程(四)】进程切换&环境变量深入剖析
linux·运维·服务器·c语言·c++·ubuntu·centos