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

坚持就是胜利

文章目录

  • 一、实例
  • 二、如何写出好(易于调试)的代码
    • 1、优秀的代码
    • 2、示范
      • (1)模拟 strcpy 函数
        • 方法一:
        • 方法二:
        • 方法三:有弊端
        • 方法四:对方法三进行优化
          • assert 的使用
        • 方法五:对方法三、方法四进行优化
          • 1、解决char*
            • 问题一:怎么返回 起始地址?
            • 解决办法
          • 2、解决const,因为 const char* source
            • 可能出现的问题
            • 修饰指针 的作用
        • 方法六:最终的正确结果
      • (2)模拟 strlen 函数
  • 三、编程常见的错误
    • 1、编译型错误(语法错误)
    • 2、链接型错误
    • 3、运行时错误
  • 四、做一个有心人,积累排错经验。


一、实例

在这里插入图片描述

在这里插入图片描述

#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数组
在这里插入图片描述

方法一:
#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;
}
方法二:
#include <stdio.h>
#include <string.h>

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

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

	return 0;
}
方法三:有弊端
#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;
}
#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;
}
#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 ,就会出错

#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 的作用:会清晰的告诉你,哪一行的代码出现了错误!

#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*
问题一:怎么返回 起始地址?
#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;
}
解决办法
#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
可能出现的问题
#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;
}

在这里插入图片描述

#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 如果放在 * 的 右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

在这里插入图片描述

#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;
}

在这里插入图片描述

#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;
}

在这里插入图片描述

方法六:最终的正确结果
#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’。

在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/407270.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SORA技术报告

文档链接&#xff1a;https://openai.com/research/video-generation-models-as-world-simulators 文章目录 Video generation models as world simulatorsTurning visual data into patchesVideo compression networkSpacetime latent patchesScaling transformers for video …

【C++】字符类型和字符数组-string

STL-容器 - string 字符串必须具备结尾字符\0 #include<iostream> #include<string> using namespace std; //STL-容器 - string char ch[101];//字符串必须具备结尾字符\0 int main() {int n; cin >> n;for (int i 0; i < n; i) {cin >> ch[i];}…

iptables和五链四表相关规则说明

文章目录 1. iptables的作用2. iptables和netfilter的联系和区别3. 四表五链说明3.1 四表3.2 五链3.3 表与链之间的包含关系 4. iptables规则的常用命令和使用方法4.1 iptables规则组成4.2 规则数据管理 5. 常用的iptables场景5.1 禁止外部主机ping内部主机5.2 禁止某些端口访问…

Linux——动态库

Linux——动态库 gcc 指令生成动态库动态库的使用动态库与静态库区别直接装到系统中软连接LD_LIBRARY_PATH直接修改动态库的配置文件 上一次我们了解了静态库&#xff0c;这次我们来了解动态库&#xff0c;如果还有没看过上一篇静态库的小伙伴&#xff0c;可以点击这里&#xf…

【深度学习笔记】3_6 代码实现softmax-regression

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 3.6 softmax回归的从零开始实现 这一节我们来动手实现softmax回归。首先导入本节实现所需的包或模块。 import torch import torchvision import numpy as np import…

LeetCode206: 反转链表.

题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 解题方法 假设链表为 1→2→3→∅&#xff0c;我们想要把它改成∅←1←2←3。在遍历链表时&#xff0c;将当前节点的 next指针改为指向前一个节点。由于节点没有引用其前一…

详细讲解缓冲区

目录 理解回车和换行&#xff08;\r&&\n&#xff09; 那如何实现单独的回车和换行呢&#xff1f; 缓冲区 证明有缓冲区的存在 ​编辑 怎么刷新缓冲区&#xff08;显示器缓冲区&#xff09;&#xff1f; fflush函数​编辑 缓冲区出现的意义 I/O流 模拟倒计时小程…

Nodejs 第四十章(prisma)

什么是 prisma? Prisma 是一个现代化的数据库工具套件&#xff0c;用于简化和改进应用程序与数据库之间的交互。它提供了一个类型安全的查询构建器和一个强大的 ORM&#xff08;对象关系映射&#xff09;层&#xff0c;使开发人员能够以声明性的方式操作数据库。 Prisma 支持…

EasyRecovery破解版补丁免费钥匙下载

说起数据恢复软件&#xff0c;相信没有小伙伴不知道EasyRecovery这个软件吧&#xff0c;该软件具有快捷、高效、便捷的特点&#xff0c;且提供的功能也非常全面&#xff0c;不仅可以恢复各样被删除的文件、视频、图片等&#xff0c;还可以支持SD卡数据恢复&#xff0c;TF卡等各…

深入浅出CChart 每日一课——快乐高四第六十一课 飞梯十二重,CChart三维曲线图绘制

同学们好&#xff0c;今天继续介绍CChart本身的功能。接下来这几节课呢&#xff0c;笨笨老师准备对CChart的三维视图和场图功能进行详细一些的介绍。本节课首先介绍三维曲线图。 CChart软件库的开发&#xff0c;首先是从二维曲线图开始的&#xff0c;这一部分经过长时间的打磨…

SpringBoot3+Vue3 基础知识(持续更新中~)

bean 把方法的返回结果注入到ioc中 1: 2: 3: 组合注解封装 实战篇&#xff1a; 解析token&#xff1a; 统一携带token&#xff1a; 驼峰命名与下划线命名转换&#xff1a; NotEmpty!!! mybatis&#xff1a; PageHelper设置后&#xff0c;会将pageNum,和pageSize自己拼接…

ubuntu22.04@Jetson Orin Nano之OpenCV安装

ubuntu22.04Jetson Orin Nano之OpenCV安装 1. 源由2. 分析3. 证实3.1 jtop安装3.2 jtop指令3.3 GPU支持情况 4. 安装OpenCV4.1 修改内容4.2 Python2环境【不需要】4.3 ubuntu22.04环境4.4 国内/本地环境问题4.5 cudnn版本问题 5. 总结6. 参考资料 1. 源由 昨天用Jetson跑demo程…

Spring Session:入门案例

Spring Session provides an API and implementations for managing a user’s session information. Spring Session提供了一种用于管理用户session信息管理的API。 Spring Session特点 传统的Servlet应用中&#xff0c;Session是存储在服务端的&#xff0c;即&#xff1a;Ses…

聚道云软件连接器:高科技企业财务自动化,提升效率准确性!

客户介绍&#xff1a; 某互联信息技术有限公司是一家专业从事信息技术服务的高科技企业&#xff0c;在业内享有较高的知名度和影响力。近年来&#xff0c;公司业务快速发展&#xff0c;对信息化建设提出了更高的要求。 客户痛点&#xff1a; 在传统情况下&#xff0c;该公司的…

【探索Linux】—— 强大的命令行工具 P.23(线程池 —— 简单模拟)

阅读导航 引言一、线程池简单介绍二、Linux下线程池代码⭕Makefile文件⭕ . h 头文件✅Task.hpp✅thread.hpp✅threadPool.hpp ⭕ . cpp 文件✅testMain.cpp 三、线程池的优点温馨提示 引言 在Linux下&#xff0c;线程池是一种常见的并发编程模型&#xff0c;它能够有效地管理…

大模型综述总结--第一部分

1 目录 本文是学习https://github.com/le-wei/LLMSurvey/blob/main/assets/LLM_Survey_Chinese.pdf的总结&#xff0c;仅供学习&#xff0c;侵权联系就删 目录如下图 本次只总结一部分&#xff0c;刚学习有错请指出&#xff0c;VX关注晓理紫&#xff0c;关注后续。 2、概述…

字符函数和字符串函数(C语言进阶)(一)

前言 C语言中对字符和字符串的处理是很频繁的&#xff0c;但是c语言本身是没有字符串类型的&#xff0c;字符串通常放在常量字符串中或着字符数组中。 字符串常量适用于哪些对它不做修改的字符串函数。 1、函数介绍 1.1 strlen strlen&#xff1a;计算字符串长度 看一个代码&…

“AI教父”李一舟翻车,中国AI培训路在何方

近日&#xff0c;AIGC领域掀起了一场不小的风波&#xff0c;知名AI博主李一舟在各大平台推出的AI课程突然下架&#xff0c;其账号遭到禁止关注的情况。 这一事件不仅引发了广泛关注和热议&#xff0c;更让许多真正想学习AIGC的用户感到迷茫和困惑&#xff1a;在众多的AIGC课程中…

ONLYOFFICE 桌面编辑器现已更新至v8.0啦

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…