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

news2024/9/22 19:38:52

坚持就是胜利

文章目录

  • 一、实例
  • 二、如何写出好(易于调试)的代码
    • 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.coloradmin.cn/o/1468195.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅析DPDK内存管理:Mempool

文章目录 前言Mempool工作机制Mempool数据结构rte_mempool_opsMempool操作接口rte_mempool_create&#xff1a;创建Mempoolrte_mempool_get&#xff1a;申请对象rte_mempool_put&#xff1a;释放对象 相关参考 前言 DPDK提供了一套内存池管理机制&#xff0c;以提供高效分配固…

了解人工智能的13个细分领域

人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;作为当今最热门和前沿的技术之一&#xff0c;已经在各种领域发挥着越来越重要的作用。随着人工智能技术的不断进步和应用&#xff0c;AI的细分领域也越来越多。目前&#xff0c;根据AI的应用领域和特…

flink源码分析 - 获取调用位置信息

flink版本: flink-1.11.2 调用位置: org.apache.flink.streaming.api.datastream.DataStream#flatMap(org.apache.flink.api.common.functions.FlatMapFunction<T,R>) 代码核心位置: org.apache.flink.api.java.Utils#getCallLocationName() flink算子flatmap中调用了一…

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

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

神经网络系列---权重初始化方法

文章目录 权重初始化方法Xavier初始化&#xff08;Xavier initialization&#xff09;Kaiming初始化&#xff0c;也称为He初始化LeCun 初始化正态分布与均匀分布Orthogonal InitializationSparse Initializationn_in和n_out代码实现 权重初始化方法 Xavier初始化&#xff08;X…

java面试题之SpringMVC篇

Spring MVC的工作原理 Spring MVC的工作原理如下&#xff1a; DispatcherServlet 接收用户的请求找到用于处理request的 handler 和 Interceptors&#xff0c;构造成 HandlerExecutionChain 执行链找到 handler 相对应的 HandlerAdapter执行所有注册拦截器的preHandler方法调…

Java知识点一

hello&#xff0c;大家好&#xff01;我们今天开启Java语言的学习之路&#xff0c;与C语言的学习内容有些许异同&#xff0c;今天我们来简单了解一下Java的基础知识。 一、数据类型 分两种&#xff1a;基本数据类型 引用数据类型 &#xff08;1&#xff09;整型 八种基本数…

计算机网络-网络互联与互联网(一)

1.常用网络互联设备&#xff1a; 1层物理层&#xff1a;中继器、集线器2层链路层&#xff1a;网桥、交换机3层网络层&#xff1a;路由器、三层交换机4层以上高层&#xff1a;网关 2.网络互联设备&#xff1a; 中继器Repeater、集线器Hub&#xff08;又叫多端口中继器&#xf…

一分钟 由浅入深 学会Navigation

目录 1.官网正式概念 1.1 初认知 2.导入依赖 2.1 使用navigation 2.2 safe Args插件-> 传递数据时用 3.使用Navigation 3.1 搭建初始框架 3.2 确定action箭头的属性 3.3 为Activity添加NavHostFragment控件 3.4 NavController 管理应用导航的对象 3.5 数据传递(单…

leetcode单调栈

739. 每日温度 请根据每日 气温 列表&#xff0c;重新生成一个列表。对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 例如&#xff0c;给定一个列表 temperatures [73, …

基于SpringBoot的停车场管理系统

基于SpringBootVue的停车场管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 前台首页 停车位 个人中心 管理员界面 摘要 摘要&#xff1a;随着城市化进程的…

基于django的购物商城系统

摘要 本文介绍了基于Django框架开发的购物商城系统。随着电子商务的兴起&#xff0c;购物商城系统成为了许多企业和个人创业者的首选。Django作为一个高效、稳定且易于扩展的Python web框架&#xff0c;为开发者提供了便捷的开发环境和丰富的功能模块&#xff0c;使得开发购物商…

Java零基础 - 条件运算符

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

Vue(学习笔记)

什么是Vue Vue是一套构建用户界面的渐进式框架 构建用户界面&#xff1a; 基于数据渲染出用户可以看到的界面 渐进式&#xff1a; 所谓渐进式就是循序渐进&#xff0c;不一定非得把Vue中的所有API都学完才能开发Vue&#xff0c;可以学一点开发一点 创建Vue实例 比如就上面…

k8s学习笔记-基础概念

&#xff08;作者&#xff1a;陈玓玏&#xff09; deployment特别的地方在于replica和selector&#xff0c;docker根据镜像起容器&#xff0c;pod控制容器&#xff0c;job、cronjob、deployment控制pod&#xff0c;job做离线任务&#xff0c;pod大多一次性的&#xff0c;cronj…

汽车常识网:电脑主机如何算功率的计算方法?

今天汽车知识网就给大家讲解一下如何计算一台主机的功率。 它还会解释如何计算计算机主机所需的功率&#xff1f; &#xff1f; &#xff08;如何计算电脑主机所需的功率&#xff09;进行说明。 如果它恰好解决了您现在面临的问题&#xff0c;请不要忘记关注本站。 让我们现在就…

vue3 vite 经纬度逆地址解析

在web端测试经纬度逆地址解析有2中方式&#xff0c;先准备好两个应用key 第一种&#xff0c;使用“浏览器端”应用类型 const address ref() const latitude ref() // 经度 const longitude ref() // 纬度 const ak 你的key // 浏览器端 function getAddressWeb() {// 创建…

【读博杂记】:近期日常240223

近期日常 最近莫名其妙&#xff0c;小导悄悄卷起来&#xff0c;说要早上八点半开始打卡&#xff0c;我感觉这是要针对我们在学校住的&#xff0c;想让我们自己妥协来这边租房子住&#xff0c;但我感觉这是在逼我养成规律作息啊&#xff01;现在基本上就是6~7点撤退&#xff0c;…

【Spring】 AOP面向切面编程

文章目录 AOP是什么&#xff1f;一、AOP术语名词介绍二、Spring AOP框架介绍和关系梳理三、Spring AOP基于注解方式实现和细节3.1 Spring AOP底层技术组成3.2 初步实现3.3 获取通知细节信息3.4 切点表达式语法3.5 重用&#xff08;提取&#xff09;切点表达式3.6 环绕通知3.7 切…

R语言入门笔记2.6

描述统计 分类数据与顺序数据的图表展示 为了下面代码便于看出颜色参数所对应的值&#xff0c;在这里先集中介绍&#xff0c; col1是黑色&#xff0c;2是粉红&#xff0c;3是绿色&#xff0c;4是天蓝&#xff0c;5是浅蓝&#xff0c;6是紫红&#xff0c;7是黄色&#xff0c;…