C语言—实用调试技巧

news2025/11/5 2:48:02

实用调试技巧

  • 什么是bug?
  • 调试是什么?有多重要?
    • 什么是调试
    • 调试的基本步骤
  • Debug和Release的介绍
  • Windows环境调试介绍
    • 调试环境的准备
    • 快捷键的使用
    • 调试的时候查看程序当前信息
        • 查看断点信息
        • 查看临时变量的值
        • 查看局部变量的值
        • 查看内存信息
        • 查看汇编信息
        • 查看寄存器信息
        • 查看调用堆栈
  • 一些调试的实例
  • 如何写出好(易于调试)的代码
    • const的作用
  • 编程常见的错误

什么是bug?

在这里插入图片描述

第一次被发现的导致计算机错误的飞蛾,也是第一个计算机程序错误。

计算机程序或者硬件里面存在的这种缺陷—bug(程序错误或程序缺陷)

调试是什么?有多重要?

找bug的过程—调试

所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。

一名优秀的程序员是一名出色的侦探。每一次调试都是尝试破案的过程。

什么是调试

迷信式调试

在这里插入图片描述
这种迷信式调试是错误的,要拒绝迷信式调试。

调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程

调试的基本步骤

  • 发现程序错误的存在
  • 以隔离、消除等方式对错误进行定位
  • 确定错误产生的原因
  • 提出纠正错误的解决办法
  • 对程序错误予以改正,重新测试

Debug和Release的介绍

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

VS编译器中的Debug和Release版本:
在这里插入图片描述

实例一:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr[i] = i + 1;
	}


	return 0;
}

在Debug环境中生成的可执行程序:

在这里插入图片描述

在这里插入图片描述

在Release环境中生成的可执行程序:

在这里插入图片描述

在这里插入图片描述

注:

  • Release版本是不能进行调试的
  • Release版本和Debug版本会有一些运行的差异,因为Release版本会自主的对代码进行各种优化,而优化了之后可能会产生结果跟Debug版本是不相同的
  • Release版本要比Debug版本生成的可执行程序的大小要小得多,因为Release版本进行了各种优化而Debug版本包含调试信息并且不作任何优化。

Windows环境调试介绍

注:linux开发环境调试工具是gdb,详情了解Linux环境基础开发工具使用

调试环境的准备

在这里插入图片描述

注:在环境中选择 debug 选项,才能使代码正常调试。

快捷键的使用

在这里插入图片描述

最常使用的几个快捷键:

  • F5:启动调试,经常用来直接调到下一个断点处。(F5跳转到程序执行过程中的逻辑上的下一个断点)
  • F9:创建断点和取消断点 断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。

在这里插入图片描述
注:F9和F5是配合使用的,这样可以提高调试的效率

  • F10:逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。

在这里插入图片描述

  • F11:逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最长用的)。

在这里插入图片描述

  • CTRL + F5:开始执行不调试,如果想让程序直接运行起来而不调试就可以直接使用

在这里插入图片描述

注:开始执行不调试即使剩了断点也可以不调试

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

查看断点信息

在调试开始之后,用于观察断点信息。

在这里插入图片描述

查看临时变量的值

在调试开始之后,用于观察变量的值。

在这里插入图片描述

在写代码的过程中监视窗口里面其实就是观察程序里面的相关信息。监视窗口里想观察什么把合理的合法的表达式放到监视窗口里就可以了。

在这里插入图片描述
自动监视窗口会监视谁要看谁的信息这都是编译器自动加进去的,自动窗口会自动的把某一些变量的值放进去监视不要了去掉。自动窗口中的数据的变化和监视跟监视窗口是一样的效果,但是监视窗口中的内容想看一直是存在的不会自动删除掉,不想要时可以删除。而自动窗口会根据自己的情况自动添加和删除信息。

一般情况下,程序员用的是监视窗口

查看局部变量的值

在调试开始之后,用于观察局部变量的值。

在这里插入图片描述

局部变量窗口监视的是程序执行到当前位置时的上下文环境中的局部变量,它会自主放到这个地方进行相关的监视

查看内存信息

在调试开始之后,用于观察内存信息。

在这里插入图片描述

查看汇编信息

在调试开始之后,有两种方式转到汇编: (1)第一种方式:右击鼠标,选择【转到反汇编】:
在这里插入图片描述

(2)第二种方式:

可以切换到汇编代码。

查看寄存器信息

在调试开始之后,用于观察寄存器信息。
在这里插入图片描述

在这里插入图片描述
可以查看当前运行环境的寄存器的使用信息。

程序在运行的过程中寄存器的值随时发生变化

查看调用堆栈

在调试开始之后,用于查看函数调用的相关信息。
在这里插入图片描述
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。

函数调用堆栈反馈的是函数调用逻辑

补充条件断点的使用:

在这里插入图片描述

注意:

  • 上面所提到的都是一些简单的调试。 以后可能会出现很复杂调试场景,例如:多线程程序的调试等。
  • 多多使用快捷键,提升效率。

一些调试的实例

实例一:

实现代码:求 1!+2!+3! …+ n! ;不考虑溢出。

int main()
{
 	int i = 0;
 	int sum = 0;//保存最终结果
 	int n = 0;
 	int ret = 1;//保存n的阶乘
 	scanf("%d", &n);
 	for(i=1; i<=n; i++)
 	{
 		int j = 0;
 		for(j=1; j<=i; j++)
 		{
 			ret *= j;
 		}
 		sum += ret;
 	}
 	printf("%d\n", sum);
 	return 0;
}

这时候如果输入3,期待输出9,但实际输出的是15。因为每次循环时(累加阶乘时)ret未置成1。

解决问题时要注意:

  1. 首先推测问题出现的原因。初步确定问题可能的原因最好。
  2. 实际上手调试很有必要。
  3. 调试的时候我们要心里有数(预期)。

调试解决的就是运行时错误

实例二:(经典笔试题)

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

运行结果是死循环。因为i和arr是两个局部变量,先创建i再创建arr,局部变量是存放在栈区上的,栈区的使用习惯是先使用高地址空间再使用低地址空间,所以内存的布局就是下图这样的。因为数组随着下标的增长地址是由低到高变化的,所以数组如果用下标来进行访问的时候只要适当的往后越界就有可能覆盖上i,就有可能导致程序的死循环 。

代码调试过程:

在这里插入图片描述
局部变量在栈区中的分布:
在这里插入图片描述

注:

  • 当程序出现死循环时,它在死循环的一直运行没有机会报错
  • 局部变量是放在栈区上的
  • 栈区内存的使用习惯是:先使用高地址空间,再使用低地址空间
  • 数组随着下标的增长地址是由低到高变化的
  • 上面i和arr的分布在VC6.0编译器下是存在0个整形的;在GCC编译器下是存在1个整型的;在VS编译器下是存在2个整型的
  • 栈的存储结构以及写代码的顺序完全有机会有可能导致程序死循环

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

优秀的代码:

  1. 代码运行正常
  2. bug很少
  3. 效率高
  4. 可读性高
  5. 可维护性高
  6. 注释清晰
  7. 文档齐全

常见的coding技巧:

  • 使用assert
  • 尽量使用const
  • 养成良好的编码风格
  • 添加必要的注释
  • 避免编码的陷阱

注:作为一名合格的程序员要预防发生错误,通过编码的技巧减少程序中错误的可能性。

实例一:

模拟实现库函数:strcpy

strcpy函数的使用:

在这里插入图片描述

#include <string.h>

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

	strcpy(arr1, arr2); //hello\0xxxx\0\0\0\0\0\0\0\0\0\0
	printf("%s\n", arr1);  //hello

	return 0;
}

strcpy()函数:是将一个字符串复制到另一块空间地址中的函数,‘\0’是停止拷贝的终止条件,同时也会将 ‘\0’ 也复制到目标空间。

strcpy函数的模拟实现:

#include <assert.h>


//strcpy函数:把src指向的内容拷贝放进dest指向的空间中
//从本质上讲,希望dest指向的内容被修改,src指向的内容不应该被修改

//strcpy 这个库函数 其实返回的是目标空间的起始地址

char* my_strcpy(char* dest, const char * src)
{
	assert(src != NULL);//断言
	assert(dest != NULL);//断言
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;//hello的拷贝
	}

	return ret;//返回目标空间的起始地址
}



int main()
{
	
	char arr1[20] = "xxxxxxxxxxx";
	char arr2[] = "hello";
	//1. 目标空间的起始地址,2. 源空间的起始地址
	printf("%s\n", my_strcpy(arr1, arr2));//链式访问
	return 0;
}

实例二:

模拟实现库函数:strlen

strlen函数用于求字符串长度

#include <assert.h>

size_t my_strlen(const char* str)
{
	//assert(str != NULL);
	assert(str);
	size_t count = 0;

	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	char arr[] = "abc";
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}

注:

  • 空指针不能解引用操作的,不能直接进行内存访问的
  • assert()—断言(如果满足某个条件时不允许发生某些事情时断言就会报错)
  • 当assert()断言时不期望某一些事情发生,当这些事情发生时assert()就会把这些错误信息报给程序员,让程序员明确知道这个错误在哪个文件的哪一行,从而很快的定位问题所在
  • assert()函数如果函数传递过来的参数使得assert函数中的条件为真什么事情都不发生,如果这个条件为假断言就会报错
  • assert函数使代码很容易可以发现问题并且把问题抛出来确定问题所在
  • assert()是一个宏,使用时引入头文件assert.h

总结:

  1. 分析参数的设计(命名,类型),返回值类型的设计
  2. 野指针的危害。
  3. assert的使用。
  4. 参数部分 const 的使用以及const修饰指针的作用
  5. 注释的添加

const的作用

实例一:

int main()
{

	//const 修饰变量,这个变量就被称为常变量,不能被修改,但是本质上还是变量

	const int num = 10;
	//num = 20;//err

	const int* p = &num;   //等价于int const * p = &num;
	int n = 100;
	//const 如果放在*的左边,修饰的是*p,表示指针指向的内容,是不能通过指针来改变的,但是指针变量本身是可以修改的      

	//*p = 20;  err
	p = &n;

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

	return 0;
}
 

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

实例二:

int main()
{
	//const 修饰变量,这个变量就被称为常变量,不能被修改,但是本质上还是变量

	const int num = 10;
	//num = 20;//err

	int* const p = &num;
	int n = 100;

	//const 如果放在*的右边,修饰的是指针变量p,表示指针变量不能被改变,但是指针指针的内容是可以被改变的

	*p = 20;
	//p = &n;//err

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

	return 0;
}
 

注:const 如果放在*的右边,修饰的是指针变量,表示指针变量不能被改变,但是指针指针的内容是可以被改变的。

const修饰指针变量总结:

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

补充:

  • int const * const * const p 第一个const修饰的是**p 第二个const修饰的是*p 第三个const修饰的是p
  • 鲁棒性和健壮性是一个意思
  • VS编译器中__cdecl—函数调用约定(函数调用传参时传参顺序由函数调用约定决定的,函数调用约定决定了函数调用里面的一些细节的一些规则)

编程常见的错误

常见的错误分类:

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

注:编译型错误一般指的是语法错误

  • 链接型错误
    看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。

注:链接型错误出现的可能要么这个符号就不存在,要么符号写错了

  • 运行时错误
    借助调试,逐步定位问题。最难搞。

补充:extern用于声明外部符号

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

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

相关文章

PERSIANN 降雨数据使用教程

一、前言PERSIANN&#xff0c;“使用人工神经网络从遥感信息中估算降水”&#xff0c;是一种基于卫星的降水检索算法&#xff0c;可提供近乎实时的降雨信息。该算法使用来自全球地球同步卫星的红外 (IR) 卫星数据作为降水信息的主要来源。 红外图像的降水基于云顶温度和降水率之…

ESP32驱动1.28寸GC9A01播放视频(一、视频分辨率的调整和视频格式的转换)

ESP32驱动1.28寸GC9A01播放视频&#xff08;一、视频分辨率的调整和视频格式的转换&#xff09;播放前准备转换视频分辨率用FFmpeg将.MP4转换为.mjpeg格式FFmpeg的win10环境搭建FFmpeg的下载环境变量的搭建MP4转换成mjpeg格式总结播放前准备 1.28寸GC9A01屏幕的分辨率是240x24…

prometheus标签

004 标签 1. 作用 Prometheus中存储的数据为时间序列&#xff0c;是由Metric的名字和一系列的标签(键值对)唯一标识的, 不同的标签代表不同的时间序列&#xff0c;即 通过指定标签查询指定数据 。 指标标签实现了查询条件的作用&#xff0c;可以指定不同的标签过滤不同的数据…

类ChatGPT产品Claude上线Slack平台,一手实测体验在此

文章目录Claude-in-Slack自我介绍成语接龙数学能力代码能力其他总结Claude-in-Slack Claude是Anthropic推出的类ChatGPT对话机器人。Anthropic是一家由前OpenAI团队成员创立的人工智能初创公司。Claude免费、支持中文、无需注册&#xff0c;好心动&#xff0c;立刻上手测试。 …

第一个禁止ChatGPT的西方国家

意大利成为第一个有效禁止 ChatGPT 的西方国家。 由于可能违反隐私和数据法&#xff0c;该国的数据监管机构已下令开发聊天机器人的 OpenAI 停止运营。 意大利数据保护局 (GPDP) 提到了一些担忧&#xff0c;包括大量收集用户数据和存储以训练 AI 算法。 ChatGPT 是一种大型语…

Eyeshot Ultimate 2023 Crack

Eyeshot Ultimate 2023 Crack 已经引入了文档类。 工作区。文档现在包含绘制场景内容所需的所有数据。 2022版GEntities已被删除。 最后&#xff0c;一个真正的跨平台中立核心产品是可用的。 新功能 曲线、平面、曲面和体积网格。 屏幕空间环境光遮挡。 托管ReadDWG和ReadDXF类…

【SQL】一文带你掌握SQL基础语法

文章目录&#x1f496;1&#xff0c;SQL概述&#x1f388;1.1 SQL简介&#x1f388;1.2 通用语法&#x1f388;1.3 SQL分类&#x1f496;2&#xff0c;DDL:操作数据库&#x1f388;2.1 查询&#x1f388;2.2 创建数据库&#x1f388;2.3 删除数据库&#x1f388;2.4 使用数据库…

哪个品牌的触控笔质量好?ipad好用的触控笔

从这一点就能看出来&#xff0c;苹果原装的这一款电容笔&#xff0c;确实是贵得让人无法入手&#xff0c;一支就要一千多块钱。实际上&#xff0c;平替电容笔对没有太多预算的用户是个不错的选择。就拿正版电容笔来说&#xff0c;一正版的电容笔来说&#xff0c;就是4支平替电容…

Mybatis-Plus——实现公共字段自动填充(瑞吉外卖)

目录 一、公共字段自动填充 1.1 问题分析 1.2 实现思路及代码编写 二、 知识补充&#xff1a; ThreadLocal 2.1 使用背景 2.2 ThreadLocal介绍 2.2.1 设置当前线程的线程局部变量的值 public void set(T value) 2.2.2 返回当前线程所对应的线程 局部变量的值 public T ge…

Linux 下 QT 安卓开发环境搭建

1、采用QT5.14.2&#xff0c;主要是安装1、jdk 2、SDK 3、NDK 三个工具包&#xff0c;版本要匹配上&#xff1b; 备注&#xff1a;配置SDK需要注意&#xff1a; 解压SDK&#xff0c;进入到tools目录下&#xff0c;运行android可执行文件&#xff0c;会打开一个页面&#xff0c…

nvm软件使用-同一个环境下控制多个不同node版本

1.使用场景 nvm是一个用于管理Node.js版本的工具&#xff0c;它可以让你在同一台机器上安装和切换不同的Node.js版本。使用nvm的好处有以下几点&#xff1a; 1.1.nvm可以让你轻松地测试你的代码在不同的Node.js版本下的兼容性和性能&#xff0c;避免因为版本差异导致的问题。…

代码随想录_二叉树_leetcode530 501

leetcode 530 二叉搜索树的最小绝对差 530. 二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6…

ChatGTP如此强大,我们普通人如何利用它来赚钱?

我从效率提升的角度&#xff0c;分享了我这段时间看到的、用到的&#xff0c;以及思考的一些内容。 最近这段时间&#xff0c;我算是密集的学习。不得不说&#xff0c;优质的资料在推特和油管上特别多&#xff0c;看科技大佬的分享真是一种享受。 很多大神也会录制各种详细的…

项目进度把控难题,项目管理系统轻松帮你解决!

项目管理是企业中非常重要的一项工作&#xff0c;无论是新的项目还是旧的项目&#xff0c;都需要对其进行有效的管理&#xff0c;以确保项目顺利完成并达到预期目标。项目管理涉及到很多方面&#xff0c;比如项目交接、实施计划管理、项目功能管理、项目设备管理等等&#xff0…

tcp三次握手与四次分手

一、tcp三次握手 1、TCP建立连接的流程 1&#xff09;client首先给server发送一个SYN报文&#xff0c;表示想要与server建立TCP连接&#xff0c;此时seq序列号为0 2&#xff09;server收到了报文后&#xff0c;向client发送一个SYN和ACK确认报文&#xff0c;将ACK和SYN放到同一…

SpringSecurity之CSRF

前言 前一篇讲解了关于用户注销以及自动登录&#xff08;记住我&#xff09;等功能。今天我们来看一下关于CSRF的使用及避免。 什么是CSRF 跨站请求伪造&#xff08;英语&#xff1a;Cross-site request forgery&#xff09;&#xff0c;也被称为 one-click attack 或者 ses…

74-网络一(基础知识)

网络一一.基础知识1.什么是网络2.互联网3.IP地址(1)什么是IP地址及其作用(2)IP地址的组成(3)IP地址的分类(4)IP地址的查询4.MAC地址5.端口号(1)端口号及其作用(2)怎么实现A主机上的一个进程和B进程上的一个进程实现通讯?网络二见:网络二 一.基础知识 1.什么是网络 网络是由…

亚马逊云科技开启您的云财务管理之旅——成本优化

众所周知&#xff0c;当你想要从头开始建立一个云财务管理计划似乎是稍有难度的。因此&#xff0c;亚马逊云科技解构了4个云财务管理CFM原则——查看、节省、计划和运营——并分享可以实施的操作指南&#xff0c;帮助您在云上取得成功。 云成本管理工具 亚马逊云科技提供一系列…

VVC之编码结构

VVC之编码结构&#xff08;新一代通用视频编码的读书笔记&#xff09;缩写概述EncAppmain函数解读缩写 缩写含义CVSCoded Video Sequence, 编码视频序列IRAPIntra Random Access Point, 帧内随机接入点GDRGradual Decoding Refresh, 逐渐解码刷新AUAccess Unit, 访问单元PUPic…

5.3 定积分的换元积分法和分部积分法

学习目标&#xff1a; 学习定积分的换元积分法和分部积分法&#xff0c;我会采取以下步骤&#xff1a; 熟悉基本概念和公式&#xff1a;首先&#xff0c;要对定积分、换元积分法和分部积分法有基本的理解&#xff0c;并掌握它们的基本公式和性质。 学习经典例题&#xff1a;找…