C++ 入门(六)— 调试程序(Debugging)

news2024/10/3 4:37:28

文章目录

  • 语法和语义错误
  • 调试(Debugging)
    • 调试过程
    • 调试策略
    • 常用的调试策略
    • 更多调试策略
  • 使用集成调试器
    • 步进(Stepping)

语法和语义错误

语法错误

编写根据 C++ 语言的语法无效的语句时,会发生语法错误。例如缺少分号、使用未声明的变量、括号或大括号不匹配等。

编译器通常会捕获语法错误并生成警告或错误。
在这里插入图片描述
语义错误

当语句在语法上有效,但没有执行时,就会发生语义错误。

有时这些会导致程序崩溃,例如:

#include <iostream>

int main()
{
    int a { 10 };
    int b { 0 };
    //1.在除以零的情况下
    std::cout << a << " / " << b << " = " << a / b << '\n'; 
	//2.没有提供初始化设定项
	int x; // no initializer provided
    std::cout << x << '\n'; 
    return 0;
}

现代编译器在检测某些类型的常见语义错误(例如,使用未初始化的变量)方面做得越来越好。

但是,在大多数情况下,编译器将无法捕获大多数此类问题,因为编译器旨在强制执行语法,而不是意图。

调试(Debugging)

调试过程

调试的一般方法,确定问题后,调试问题通常包括五个步骤:

  1. 找到根本原因
  2. 了解问题
  3. 确定修复
  4. 修复问题
  5. 复检

示例:

#include <iostream>

int add(int x, int y) // 这个函数应该执行加法
{
    return x - y; // 但使用了错误的运算符
}

int main()
{
    std::cout << "5 + 3 = " << add(5, 3) << '\n'; // 结果应该是8,但是2
    return 0;
}

按照调试问题通常包括五个步骤进行调试:

  • 找到根本原因:在第 10 行,我们可以看到我们正在传递参数(5 和 3)的文本,因此没有出错的余地。由于函数 add 的输入是正确的,但输出不正确,因此很明显,函数添加一定产生了错误的值。
  • 了解问题:在这种情况下,很明显为什么会生成错误的值 ,因为我们使用了错误的运算符。
  • 确定修复:我们只需将 operator- 更改为 operator+
  • 修复问题 :将 operator- 更改为 operator+ 并确保程序重新编译。
  • 复检:实现更改后,重新运行程序将指示我们的程序现在生成正确的值 8。

此示例微不足道,但说明了诊断任何程序时将经历的基本过程。

调试策略

在调试程序时,在大多数情况下,您的绝大多数时间将花费在试图找到错误的实际位置上。

发现问题后,其余步骤(修复问题并验证问题是否已解决)相比之下通常是微不足道的。

两种找错误实际位置方式:

通过代码检查发现问题

int main()
{
    getNames(); // 要求用户输入一堆名字
    sortNames(); // 把它们按字母顺序排序
    printNames(); // 打印已排序的名称列表

    return 0;
}

如果希望此程序按字母顺序打印名称,但它以相反的顺序打印它们,则问题可能出在 sortNames 函数中。如果可以将问题范围缩小到特定函数,则只需查看代码即可发现问题。

通过运行程序来查找问题

观察程序运行时的行为,并尝试从中诊断问题。

这种方法可以概括为:
1、弄清楚如何重现问题;
2、运行程序并收集信息以缩小问题所在;
3、重复上述步骤,直到找到问题;

常用的调试策略

调试策略 #1:注释掉代码

如果程序表现出错误行为,减少必须搜索的代码量的一种方法是注释掉一些代码并查看问题是否仍然存在。

int main()
{
    getNames(); 
//    doMaintenance(); 
    sortNames(); 
    printNames();

    return 0;
}

调试策略 #2:验证代码流

在更复杂的程序中常见的另一个问题是程序调用函数的次数过多或过少(包括根本不调用)。

在这种情况下,将语句放在函数的顶部以打印函数的名称会很有帮助。这样,当程序运行时,您可以看到正在调用哪些函数。

#include <iostream>

int getValue()
{
	return 4;
}

int main()
{
    std::cout << getValue << '\n';

    return 0;
}

尽管我们希望此程序打印值 4,但它应该打印值1。
在这里插入图片描述
这时,添加临时调试语句,可以确认 getValue()韩式是否被调用。

#include <iostream>

int getValue()
{
std::cerr << "getValue() called\n";
	return 4;
}

int main()
{
std::cerr << "main() called\n";
    std::cout << getValue << '\n';

    return 0;
}

在这里插入图片描述
现在我们可以看到函数 getValue 从未被调用过。

修改后:

#include <iostream>

int getValue()
{
std::cerr << "getValue() called\n";
	return 4;
}

int main()
{
std::cerr << "main() called\n";
    std::cout << getValue() << '\n';

    return 0;
}

现在,这将产生正确的输出:
在这里插入图片描述

调试策略 #3:打印值

对于某些类型的错误,程序可能会计算或传递错误的值。

我们还可以输出变量(包括参数)或表达式的值,以确保它们是正确的。

int add(int x, int y)
{
	std::cerr << "add() called (x=" << x << ", y=" << y << ")\n";
	return x + y;
}
int main()
{
	int z{ add(x, y) };
	std::cerr << "main::z = " << z << '\n';
	printResult(z);

	return 0;
}

打印语句进行调试劣势

  1. 调试语句会使代码混乱。
  2. 调试语句使程序的输出混乱。
  3. 调试语句需要修改代码以添加和删除代码,这可能会引入新的错误。
  4. 调试语句完成后必须将其删除,这使得它们不可重用。

更多调试策略

对调试代码进行条件化

完成调试语句后,需要删除它们,或者注释掉它们。然后,如果您稍后再次想要它们,则必须重新添加它们,或者取消注释它们。

#include <iostream>

#define ENABLE_DEBUG // 注释掉以关闭调试

int getUserInput()
{
#ifdef ENABLE_DEBUG
	std::cerr << "getUserInput() called\n";
#endif
	std::cout << "Enter a number: ";
	int x{};
	std::cin >> x;
	return x;
}

int main()
{
#ifdef ENABLE_DEBUG
	std::cerr << "main() called\n";
#endif
    int x{ getUserInput() };
    std::cout << "You entered: " << x << '\n';

    return 0;
}

使用日志

日志是已发生事件的连续记录,通常带有时间戳。

生成日志的过程称为日志记录。通常,日志会写入磁盘上的文件(称为日志文件),以便以后可以查看。

日志文件的优点

1、由于写入日志文件的信息与程序的输出是分开的,因此可以避免因混合正常输出和调试输出而导致的混乱。
2、日志文件也可以很容易地发送给其他人进行诊断。

使用集成调试器

步进(Stepping)

调试器

调试器是一种计算机程序,它允许程序员控制另一个程序的执行方式,并在该程序运行时检查程序状态。
例如,程序员可以使用调试器逐行执行程序,并在此过程中检查变量的值。通过将变量的实际值与预期值进行比较,或通过代码观察执行路径,调试器可以极大地帮助跟踪语义(逻辑)错误。

调试器背后的功能是双重的:精确控制程序执行的能力,以及查看(并根据需要修改)程序状态的能力。

在VSCode中配置调试器

  • 请按 Ctrl+Shift+P 并选择“C/C++:添加调试配置;
    在这里插入图片描述 - 然后选择“C/C++:g++ 生成和调试活动文件,这应该创建并打开配置文件;
    在这里插入图片描述

  • 将“stopAtEntry”更改为 true:
    在这里插入图片描述

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

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

相关文章

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

目录 前言 接上篇&#xff1a; 1.7 strtok 1.8 strerror 1.9 字符分类函数 总结 前言 C语言中对字符和字符串的处理是很频繁的&#xff0c;但是c语言本身是没有字符串类型的&#xff0c;字符串通常放在常量字符串中或着字符数组中。 字符串常量适用于那些对它不做修改的字…

生成式 AI - Diffusion 模型的数学原理(5)

来自 论文《 Denoising Diffusion Probabilistic Model》&#xff08;DDPM&#xff09; 论文链接&#xff1a; https://arxiv.org/abs/2006.11239 Hung-yi Lee 课件整理 讲到这里还没有解决的问题是&#xff0c;为什么这里还要多加一个噪声。Denoise模型算出来的是高斯分布的均…

根据前序后序遍历求出二叉树

根据前序后序遍历求出二叉树 一、题目描述 给定两个整数数组&#xff0c;preorder 和 postorder &#xff0c;其中 preorder 是一个具有 无重复 值的二叉树的前序遍历&#xff0c;postorder 是同一棵树的后序遍历&#xff0c;重构并返回二叉树。 二、题目分析 需求&#xff…

成都直播基地作为产业重要载体,引领直播行业健康、多元发展

近年来&#xff0c;我国网络直播行业呈现出井喷式的发展态势。众多直播平台如雨后春笋般涌现&#xff0c;直播内容丰富多样&#xff0c;涵盖游戏、电竞、美食、旅游、教育等多个领域。同时&#xff0c;成都直播产业园规模持续扩大&#xff0c;产业不断完善&#xff0c;整体呈现…

如何在Win系统搭建Oracle数据库并实现远程访问【内网穿透】

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

基于Java SSM框架实现高考填报信息系统项目【项目源码】计算机毕业设计

基于java的SSM框架实现高考填报信息系统演示 JAVA简介 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterprise JavaBeans&#xff09;的全面支持&#xff0c;java servlet API&#xff0c;JSP&#xff08;java serv…

【前沿热点视觉算法】-带有信道坐标注意特征融合模块的双光谱语义分割网络

计算机视觉算法分享。问题或建议&#xff0c;请文章私信或者文章末尾扫码加微信留言。 1 论文题目 带有信道坐标注意特征融合模块的双光谱语义分割网络 2 论文摘要 双光谱&#xff08;RGB-hehtir&#xff09;语义分割是在恶劣成像环境&#xff08;如黑暗、雨、雾&#xff09…

SpringCloud Alibaba 2022之Nacos学习

SpringCloud Alibaba 2022使用 SpringCloud Alibaba 2022需要Spring Boot 3.0以上的版本&#xff0c;同时JDK需要是17及以上的版本。具体的可以看官网的说明。 Spring Cloud Alibaba版本说明 环境搭建 这里搭建的是一个聚合项目。项目结构如下&#xff1a; 父项目的pom.xm…

还不知道随身WiFi这个蓝海市场怎么做?这个一定要看!适合30-40岁轻资产小生意

有没有发现你身边的人最近都在失业&#xff1f;无论是国企的、事业编的、又或者是民营企业的都在欠薪或者失业&#xff0c;看来经济寒潮是真的来了。虽然经济大环境不好&#xff0c;但是仍然涌现出了物联网、人工智能、大数据等新兴的蓝海市场。可是很多创投圈的朋友都表示&…

力扣随笔之按奇偶排序数组(简单905)

思路1&#xff1a;根据双指针对撞指针的思路&#xff0c;定义一个左指针从数组前端开始遍历&#xff0c;定义一个右指针从后端开始遍历&#xff0c;这时候有四种情况 左奇右偶&#xff1a;这种情况需要将其位置交换&#xff0c;将偶数提前&#xff0c;奇数后移左奇右奇&#xf…

如何优化小程序关键词,提高搜索结果排名

在数字化时代&#xff0c;小程序作为一种轻量级的应用形态&#xff0c;已成为用户获取信息、享受服务的重要渠道。然而&#xff0c;随着小程序数量的不断增加&#xff0c;如何在搜索结果中脱颖而出&#xff0c;成为了每一个小程序开发者需要面对的问题。其中&#xff0c;关键词…

LeetCode 0235.二叉搜索树的最近公共祖先:用搜索树性质(不遍历全部节点)

【LetMeFly】235.二叉搜索树的最近公共祖先&#xff1a;用搜索树性质&#xff08;不遍历全部节点&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/ 给定一个二叉搜索树, 找到该树中两个指定节点的最近公…

软硬协同设计下的飞天盘古,是如何降低存储系统开销的?

云布道师 经过十几年的技术演进&#xff0c;阿里巴巴已经实现了统一存储的目标——即以“飞天盘古”系统作为统一底座&#xff0c;通过标准化、服务化和开放化的方式建立了完整的存储产品和服务体系&#xff0c;服务广大内部和外部客户。 “万古乾坤心上辟&#xff0c;于令日…

x86使用execve执行一个elf文件并传递参数--代码思路分析

execve(const char *filename, char *const argv[ ], char *const envp[ ]) 视频教程以及实际代码可以看这一个教程 其他的需要的知识 GDT表 GDT表虚拟内存 页表 任务切换 fork实现 elf文件加载 这一个是一个Linux下面的标准接口 这一个的实际作用的是执行一个可执行文件 把当…

用最直观的方式解释:什么是零信任?

在网络安全领域&#xff0c;零信任&#xff08;Zero Trust&#xff09;是一种新兴的安全模型&#xff0c;旨在提高网络安全防御的效果。零信任的核心理念是不信任任何人或设备&#xff0c;即使是内部的用户或设备也不例外。这一概念可以通过一个简单易懂的故事来解释。 很久以…

Opencv(2)深浅拷贝与基本绘图(c++python

Opencv(2)深浅拷贝与基本绘图 文章目录 Opencv(2)深浅拷贝与基本绘图三、深浅拷贝四、HSV色域(1).意义(2).cvtColor()(3).inRange()(4).适应光线 三、深浅拷贝 浅拷贝是指当图像之间进行赋值时&#xff0c;图像数据并未发生复制&#xff0c;而是两个对象都指向同一块内存块。 …

NCDA视觉传达设计大赛终极攻略:助你斩获佳绩

第十二届全国高校未来设计师数字艺术设计大赛&#xff08;NCDA&#xff09; A类&#xff1a;视觉传达设计 参赛对象&#xff1a; 大学生小组&#xff1a;分①研究生组②本科生组③专科生组&#xff0c;三组分别进行评审 教师小组&#xff1a;普通高校教师&#xff0c;不分小…

森歌集成灶:以冠军标准打造健康厨房,为全民健康保驾护航

在2024年这个实施“十四五”规划的关键之年&#xff0c;健康话题无疑是公众最为关注的焦点之一。随着国家卫健委最新发布的《2022年中国居民健康素养监测情况》报告显示&#xff0c;我国居民健康素养水平稳步提升&#xff0c;厨电高端品牌森歌响应国策、顺应潮流将于2月27日-2月…

【Golang】Golang使用embed加载、打包静态资源文件

【Golang】Golang使用embed加载、打包静态资源文件 大家好 我是寸铁&#x1f44a; 总结了一篇Golang使用embed加载静态资源文件的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 事情是这样的&#xff1a;前不久&#xff0c;有同学问我,golang怎么把静态资源文件打包成一…

ReentrantLock详解-可重入锁-默认非公平

ReentrantLock是Java中的一个可重入锁&#xff0c;也被称为“独占锁”。它基于AQS&#xff08;AbstractQueuedSynchronizer&#xff09;框架实现&#xff0c;是JDK中提供的一种线程并发访问的同步手段&#xff0c;与synchronized类似&#xff0c;但具有更多特性。 ReentrantLo…