Visual Studio里的调试(debugging)功能介绍
参考
1- Introduction to Debugging | Basic Visual Studio Debugging(这是一位印度博主视频,我下面做到笔记也主要参考她的视频,但不得不说口音太重了,一股咖喱味)
目录
- 个人对调试浅显的认识和对调试的介绍
- 逐行调试(step through code)
- 断点(breakpoint)
- 数据显示(datatips)
- 监控(watch window)
- 快速监视(quick watch)
- 自动变量和局部窗口(auto and local window)
- 即时窗口(immediate window)
- 调用堆栈(call stack)
个人对调试浅显的认识和对调试的介绍
介绍
调试的历史起源
调试的起源可以追溯到计算机发展的早期阶段。在 20 世纪 40 年代,世界上第一台通用电子计算机 ENIAC 诞生后不久,调试就成为了必要的工作。当时,计算机系统由大量的电子管、继电器等组成,硬件故障频繁出现。
有一个著名的事件标志着 “调试” 一词的正式诞生。1947 年,Grace Hopper 团队在哈佛大学的 Mark II 计算机上工作时,发现计算机故障是由于一只飞蛾被困在继电器中导致的。他们将飞蛾取出并贴在工作日记上,还注明 “First actual case of bug being found”,从此 “bug”(虫子,代表计算机故障)和 “debugging”(调试,即排除故障)这两个术语就流传开来。最初调试主要是针对硬件故障,通过检查电路连接、更换损坏的电子元件等方式来解决问题。
调试的发展
- 早期阶段(硬件调试为主):在计算机发展初期,调试主要围绕硬件展开。工程师们使用各种测试设备,如逻辑分析仪、示波器等,来检测电路中的信号和故障。由于计算机系统结构相对简单,调试方法也比较直观,主要是通过手动检查和测试来定位问题。
- 软件调试的兴起:随着计算机软件的发展,程序变得越来越复杂,软件故障也日益增多。从早期的汇编语言程序到高级编程语言编写的大型应用程序,调试的重点逐渐从硬件转向软件。最初的软件调试工具非常简陋,可能只是一些简单的打印语句,用于输出程序运行过程中的关键信息。
- 集成开发环境(IDE)的出现:20 世纪 80 年代和 90 年代,集成开发环境开始流行。IDE 将编辑、编译、调试等功能集成在一起,大大提高了开发效率。调试工具也得到了极大的改进,提供了诸如设置断点、单步执行、查看变量值等功能,使得开发者可以更方便地定位和解决软件中的问题。
- 现代调试技术:随着计算机技术的不断发展,调试技术也在不断进步。现在的调试工具支持多线程调试、远程调试、性能分析等功能。同时,调试技术也与人工智能、机器学习等领域相结合,能够自动检测和诊断一些常见的软件问题。
为什么使用调试
- 定位和解决问题:调试是发现和修复程序中错误(bug)的最有效方法。通过调试工具,开发者可以逐步执行程序,观察变量的值和程序的执行流程,从而找出导致程序出现异常的原因。
- 理解程序行为:在开发过程中,调试可以帮助开发者深入理解程序的运行机制。通过观察程序在不同条件下的执行情况,开发者可以更好地掌握程序的逻辑和性能特点。
- 优化程序性能:调试工具不仅可以用于查找错误,还可以用于性能分析。通过分析程序的执行时间、内存使用情况等指标,开发者可以找出程序中的性能瓶颈,并进行优化。
各个平台调试工具的差异
- 桌面开发平台(如 Windows、macOS、Linux)
- Windows:Visual Studio 是 Windows 平台上最常用的集成开发环境,它提供了强大的调试功能,支持多种编程语言(如 C++、C#、VB.NET 等)。调试工具包括断点设置、单步执行、变量查看、调用栈分析等,还支持多线程调试和远程调试。此外,Windows 还有 WinDbg 这样的专业调试工具,主要用于调试内核模式代码和分析系统级故障。
- macOS:Xcode 是苹果公司为 macOS 平台提供的集成开发环境,主要用于开发 macOS、iOS、iPadOS 等系统的应用程序。Xcode 的调试工具功能丰富,支持 Swift、Objective - C 等编程语言。它提供了图形化的调试界面,方便开发者进行代码调试和性能分析。另外,LLDB 是 Xcode 内置的调试器,具有强大的命令行调试功能。
- Linux:在 Linux 平台上,GDB(GNU Debugger)是最常用的调试工具,它支持多种编程语言,如 C、C++、Fortran 等。GDB 是一个命令行调试器,通过一系列的命令来控制程序的执行和查看程序状态。同时,许多集成开发环境(如 Eclipse、Code::Blocks 等)也支持在 Linux 上使用,它们集成了 GDB 的功能,提供了图形化的调试界面。
- 移动开发平台(如 Android、iOS)
- Android:Android Studio 是 Android 开发的官方集成开发环境,它提供了专门的调试工具,支持 Java、Kotlin 等编程语言。调试功能包括设置断点、查看变量、监控线程等,还可以模拟不同的设备和系统环境进行调试。此外,Android 还提供了 Logcat 工具,用于查看应用程序的日志信息,帮助开发者定位问题。
- iOS:如前所述,Xcode 是 iOS 开发的主要工具,其调试功能同样适用于 iOS 应用程序的开发。开发者可以在模拟器或真机上进行调试,查看应用程序的运行状态和性能指标。另外,Instruments 是 Xcode 中的一个性能分析工具,可用于分析应用程序的内存使用、CPU 性能、网络请求等方面的问题。
- 嵌入式开发平台:嵌入式系统的调试通常具有一定的特殊性,因为嵌入式设备的资源有限,且可能没有完整的操作系统支持。常见的调试工具包括 JTAG(Joint Test Action Group)调试器、仿真器等。这些工具可以通过特定的接口与嵌入式设备进行连接,实现对程序的调试和监控。不同的嵌入式芯片厂商也会提供相应的开发工具和调试环境,以满足开发者的需求。
如何在VS里调试
代码写好后可以以两种方式运行,一种是开始调试,另一种是开始执行。平时我们都是使用开始执行不调试。
我对调试的认识
因为才只是学完C++基础语法,高级特性还没有怎么了解,也不了解什么并发编程,什么线程等概念。之所以主动去学习调试,是因为在学习算法和数据结构过程中总是因为逻辑错误而花费大量时间就修改。
在编写代码时总共会遇到三类错误:
- 语法错误(syntax errors)
- 运行错误(runtime errors)
- 逻辑错误 (logical errors)
其中前两个的错误非常好解决,现在编译器都非常智能,在代码编译前它就会给你指出哪里有语法错误,也会有警告。如果有问题,编译时根本就不会通过。
至于运行错误,运行出错时程序终止,会抛出异常和错误信息。
更何况C++还有异常捕获语法来处理程序运行过程出现异常
反而是一些逻辑错误不容易发现,往往是看到输出结果与期望不一致时,就开始绞尽脑汁思考程序运行过程。以前我没学会调试时,就是使用return大法,相当于断点的低配版,甚至用笔和纸画出运行流程。现在好了,我可以逐句测试代码,随时查看代码中变量的变化且修改。
我逐渐体会到,写代码的过程其实和造火箭有异曲同工之妙。过去,我总是害怕代码报错,追求像火箭发射一样的一次成功,因此在前期投入大量时间进行修改和检查。但现在我意识到,真正要快速完成代码或实现某项功能,需要的是拥抱错误,从经验中学习。就像马斯克造星舰一样,每一次爆炸都是一次宝贵的数据收集和改进机会,从而大大加快了研发速度。写代码也是如此,我们不应害怕报错和崩溃,而应在一次次的失败中积累经验,快速迭代,最终达到目标。
逐行调式(step through code)
看英文一下就明白这个功能是什么意思,“穿过代码”,遍历每个语句,经历每个细节。
有三个选项:逐语句(step into),逐过程(step over),跳出(step out)。
逐语句
逐语句是一种最细致的调试方式。当使用逐语句调试时,调试器会按照程序代码的执行顺序,一条语句一条语句地执行。对于包含函数调用、方法调用、循环语句、条件语句等各种复杂结构的代码,它都会深入到每一个细节中。例如,遇到函数调用时,它会进入被调用的函数内部,从函数的第一条语句开始继续逐语句执行
使用场景
- 深入分析函数内部逻辑:当开发者对某个函数的实现逻辑不太清楚,或者怀疑函数内部存在问题时,使用逐语句可以深入函数内部,查看每一步的执行情况,检查变量的赋值、计算过程等是否正确。
- 追踪复杂算法的执行过程:对于一些复杂的算法,如递归算法、图算法等,通过逐语句调试可以清晰地看到算法的每一次迭代、每一个分支的执行情况,有助于理解算法的执行流程和发现潜在的逻辑错误。
逐过程
逐过程调试与逐语句类似,但有一个重要区别。在遇到函数调用时,逐过程不会进入被调用函数的内部去逐语句执行,而是把整个函数调用看作一条语句,直接执行完这个函数,并返回函数的执行结果,然后继续执行下一条语句。它主要关注的是当前代码行的整体执行效果,而不是函数内部的具体执行过程。
跳出
跳出操作主要用于在已经进入某个函数内部进行调试的情况下,快速返回到调用该函数的代码位置。当使用逐语句或其他方式进入一个函数后,如果已经完成了对该函数内部部分代码的调试,或者不想继续在该函数内部执行剩余的代码,就可以使用跳出操作。它会立即执行完当前函数中剩余的代码,并返回到调用该函数的下一条语句处,继续进行调试。
练习调试
第一步,要想运行调试模式,首先让程序处于debug模式
第二步,在调试菜单栏里点击调试或者按快捷键F11,从main函数的第一个语句开始(在没有设置断点前不要点击调试,这样会一直运行)
点击后如下
屏幕录制 2025-03-03 125601
断点(breakpoint)
断点是在编程调试过程中设置的一个特定位置标记,用于暂停程序的执行,以便开发者检查程序的状态、变量的值、调用栈等信息,帮助查找和解决程序中的问题。以下是关于断点的详细介绍:
断点的作用
- 暂停程序执行:在程序运行到断点处时,会自动暂停,让开发者有机会在特定时刻检查程序的运行状态,包括变量的值、内存的使用情况、对象的属性等,有助于分析程序是否按照预期执行。
- 观察变量变化:通过在断点处查看变量的值,可以了解程序在运行过程中变量的变化情况,判断变量的赋值、计算等操作是否正确,从而发现逻辑错误或数据处理问题。
- 跟踪程序执行路径:可以帮助开发者清晰地了解程序的执行流程,确定程序是否按照预期的逻辑路径执行,是否存在意外的分支或循环问题。
- 调试函数调用:在函数调用前后设置断点,能够查看函数的参数传递是否正确,函数的返回值是否符合预期,以及函数内部的执行情况,有助于排查函数实现中的问题。
- 分析多线程程序:在多线程程序中,断点可以用于暂停特定线程的执行,方便检查该线程的状态、与其他线程的交互情况,以及排查线程同步、资源竞争等问题。
断点的使用方法
不同的编程开发环境使用断点的方式可能略有不同,但一般都遵循以下基本步骤:
- 设置断点 :通常在代码行的左侧空白处点击或使用特定的快捷键来设置断点。例如在 Visual Studio 中,可以在代码行左侧边距处单击鼠标左键设置断点,也可以将光标定位到要设置断点的代码行,然后按下
F9
键设置断点。
或者在按下F9,在光标所在行设置断点。
- 启动调试:设置好断点后,通过开发环境的调试启动按钮或快捷键来启动调试会话。在 Visual Studio 中,可以点击 “调试” 菜单 -> “开始调试”,或者使用
F5
键启动调试 - 程序暂停在断点处:当程序执行到设置的断点位置时,会自动暂停运行。此时,开发环境会切换到调试视图,显示当前程序的执行状态,包括当前代码行、变量的值、调用栈等信息。
- 进行调试操作
- 查看变量值:可以在调试工具的变量窗口中查看当前作用域内的变量值,也可以将鼠标悬停在代码中的变量上,通过弹出的提示框查看变量的值。
- 单步执行:使用逐语句(Step Into)、逐过程(Step Over)、跳出(Step Out)等调试操作,进一步查看程序的执行细节,分析程序的运行情况。
- 查看调用栈:通过调用栈窗口,可以查看当前函数的调用层次结构,了解函数的调用顺序和上下文关系。
- 清除或修改断点
- 清除断点:如果已经完成了对某个断点的调试任务,不再需要该断点,可以在代码编辑器中再次点击断点所在位置或使用快捷键来清除断点。在 Visual Studio 中,再次按下
F9
键可以清除当前行的断点. - 修改断点条件:有些开发环境支持为断点设置条件,只有当条件满足时程序才会在该断点处暂停。例如在 Visual Studio 中,可以右键单击断点,选择 “条件”,然后设置条件表达式,只有当条件表达式的值为真时,程序才会在该断点处暂停。
- 清除断点:如果已经完成了对某个断点的调试任务,不再需要该断点,可以在代码编辑器中再次点击断点所在位置或使用快捷键来清除断点。在 Visual Studio 中,再次按下
补充
禁用断点
如果你觉得一个个删掉断点很麻烦时,可以一键让所有断点失效(disable)。
你也可以在此单独启用某个断点,实现精准监控。
数据显示(DataTips)
DataTips 即数据提示,是调试环境里的一项功能。当程序在调试模式下暂停执行时,开发者只需将鼠标指针悬停在代码中的变量上,就会弹出一个小窗口,这个窗口就是 DataTips,它会显示该变量的当前值、类型等相关信息。
通过将鼠标放到对应变量名上,就会显示该变量在该作用域下的值。
作用
- 快速查看变量值
- 在调试过程中,开发者常常需要了解程序运行时各个变量的值是否符合预期。使用 DataTips 无需在调试窗口中手动查找和添加变量进行监视,只需将鼠标悬停在变量上,就能立即看到变量的当前值,大大节省了时间和操作步骤。
- 例如,在调试一个复杂的算法时,算法中涉及多个中间变量,通过 DataTips 可以快速查看每个变量在不同执行阶段的值,帮助开发者理解算法的执行过程和结果。
- 实时跟踪变量变化
- 随着程序的单步执行,DataTips 会实时更新显示变量的值。开发者可以逐行执行代码,同时观察变量值的变化情况,从而清晰地了解程序的执行逻辑和数据流向。
- 比如,在一个循环结构中,使用 DataTips 可以直观地看到循环变量每次迭代后的变化,判断循环是否按照预期进行,是否存在逻辑错误。
- 辅助调试复杂数据结构
- 对于数组、结构体、对象等复杂数据结构,DataTips 不仅能显示其整体信息,还可以展开查看其内部成员的详细信息。通过这种方式,开发者可以深入了解复杂数据结构的内容和状态。
- 以一个包含多个属性的对象为例,将鼠标悬停在对象变量上,DataTips 会显示对象的基本信息,点击展开箭头后,可以查看对象的各个属性及其对应的值,方便排查对象属性赋值或使用过程中可能出现的问题。
- 提高调试效率
- DataTips 提供了一种直观、便捷的方式来获取变量信息,避免了频繁切换到调试窗口查看变量的操作,使开发者能够更专注于代码本身和调试过程。尤其是在调试大型项目时,这种高效的变量查看方式可以显著提高调试效率,快速定位和解决问题。
监控(Watch Window)
Watch Window(监视窗口)是集成开发环境(IDE)如 Visual Studio 中一个非常实用的调试工具,它允许开发者在调试程序时实时监控变量、表达式的值及其变化情况。以下为你详细介绍:
功能概述
在调试过程中,开发者往往需要持续关注某些变量或表达式的值,以此来验证程序的运行逻辑是否正确。Watch Window 提供了一个专门的区域,让开发者可以添加想要监控的对象,无论程序执行到哪一步,都能方便地查看这些对象的最新值。
打开方式
- 在调试状态下,选择菜单栏中的 “调试” -> “窗口” -> “监视”,然后可以选择 “监视 1”“监视 2”“监视 3” 或 “监视 4”,这些窗口的功能是一样的,你可以根据需要同时打开多个监视窗口。
- 也可以使用快捷键
Ctrl + Alt + W
后再按相应的数字键(如1
对应 “监视 1”)来快速打开。
使用方法
1. 添加监视对象
- 手动输入:在 Watch Window 的空白行中,直接输入你想要监控的变量名或表达式。例如,如果你有一个变量
int num = 10;
,在监视窗口的空白行输入num
,按下回车键,该变量就会被添加到监视列表中。
- 从代码中添加:在代码编辑器中选中要监控的变量或表达式,右键单击并选择 “添加监视”,该对象会自动添加到 Watch Window 中。
2. 查看和更新值
当程序在调试模式下运行时,Watch Window 会实时显示所监控对象的值。随着程序的执行,这些值会不断更新。如果程序暂停在某个断点处,你可以清楚地看到当前状态下各个变量和表达式的值。
你甚至可以监控表达式,点击右边环形箭头计算,注意表达式会不会改变其他变量的值,即有没有副作用。
3. 编辑和删除监视对象
- 编辑:如果需要修改监视的对象,双击 Watch Window 中相应的名称,即可进行编辑。
- 删除:选中要删除的监视对象,按下
Delete
键或者右键单击并选择 “删除监视”。
优势和应用场景
- 多变量监控:可以同时监控多个变量和表达式,方便对比和分析它们之间的关系。例如,在一个计算平均值的程序中,你可以同时监控总和、数量和平均值这几个变量。
- 复杂表达式监控:不仅可以监控单个变量,还可以监控复杂的表达式。比如,对于代码
int a = 5, b = 3; int result = (a + b) * 2;
,你可以在监视窗口输入(a + b) * 2
来监控这个表达式的计算结果。 - 调试循环和递归:在调试循环或递归代码时,通过 Watch Window 可以清晰地看到变量在每次迭代或递归调用中的变化情况,有助于发现逻辑错误。
快速监视(quick watch)
Quick Watch(快速监视)是集成开发环境(IDE)中一个方便调试的工具,在 Visual Studio 等软件中广泛应用。它允许开发者在调试过程中快速查看和评估变量、表达式的值。下面从多个方面详细介绍 Quick Watch:
功能概述
在调试程序时,开发者可能需要临时查看某个变量或表达式的值,而不必专门打开完整的监视窗口来添加监视项。Quick Watch 提供了一种即时的、临时的方式来查看这些值,提高调试效率。
打开方式
- 快捷键:在 Visual Studio 中,当程序处于调试暂停状态(如遇到断点)时,选中想要查看的变量或表达式,按下
Shift + F9
组合键,即可打开 Quick Watch 对话框。
- 菜单操作:也可以在选中变量或表达式后,右键单击并选择 “快速监视” 选项。
使用方法
1. 查看变量值
当打开 Quick Watch 对话框时,选中的变量或表达式会自动显示在 “表达式” 文本框中。点击 “重新计算” 按钮(通常是一个带有向左箭头的圆形图标),对话框下方会显示该变量或表达式的当前值。
2. 输入自定义表达式
如果想要查看其他表达式的值,可直接在 “表达式” 文本框中输入相应的表达式,然后点击 “重新计算” 按钮,即可得到该表达式的计算结果。例如,在一个包含变量 int a = 5; int b = 3;
的程序中,你可以在文本框中输入 a + b
来查看它们的和。
3. 类型和其他信息查看
除了值之外,Quick Watch 对话框还会显示变量或表达式的类型等相关信息,帮助你更全面地了解其特性。
与 Watch Window 的区别
- 临时性:Quick Watch 主要用于临时查看变量或表达式的值,适用于一次性的、快速的检查需求。而 Watch Window 则用于长期监控多个变量和表达式,你可以在其中添加多个监视项,并且在整个调试过程中持续查看它们的值。
- 操作便捷性:Quick Watch 的操作更加简洁快速,只需选中目标并按下快捷键即可打开查看。而 Watch Window 的设置相对复杂一些,需要手动添加监视项,但它提供了更丰富的管理功能,如编辑、删除监视项等。
应用场景
- 快速验证:当你在调试过程中突然想验证某个变量或表达式的值时,使用 Quick Watch 可以迅速得到结果,无需中断调试流程去设置监视窗口。
- 临时计算:对于一些临时的计算需求,如在调试时想快速计算某个复杂表达式的值,Quick Watch 可以方便地完成这个任务。
自动变量(auto window) 和局部变量(local window)
在 Visual Studio 等开发工具的调试环境中,自动变量窗口(Auto Window)和局部变量窗口(Locals Window)是两个非常实用的工具,它们可以帮助开发者在调试过程中查看变量的值,以下是对它们的详细介绍:
自动变量窗口(Auto Window)
功能概述
自动变量窗口会自动显示当前正在执行的代码行附近所使用的变量。这些变量通常是当前语句以及上一条语句中涉及到的变量,IDE 会智能地识别并将它们展示在这个窗口中,方便开发者快速查看相关变量的状态。
同样,在DataTips那里,你也可以点击倒三角进一步查看内部成员。
使用场景
- 快速查看上下文变量:当你单步调试代码时,自动变量窗口会动态更新,显示当前执行语句相关的变量。例如,在执行一个函数调用时,它会显示调用函数前和调用函数过程中涉及的变量,让你能够快速了解函数调用前后变量的变化。
- 调试复杂表达式:在处理包含多个变量的复杂表达式时,自动变量窗口可以清晰地显示每个变量的值,帮助你理解表达式的计算过程。
局部变量窗口(Locals Window)
功能概述
局部变量窗口会显示当前作用域内的所有局部变量。局部变量是在函数内部或代码块内部定义的变量,它们的生命周期仅限于所在的作用域。局部变量窗口会将这些变量罗列出来,并显示它们的当前值。
使用场景
- 全面了解局部变量状态:当你需要查看某个函数或代码块内所有局部变量的状态时,局部变量窗口是一个很好的选择。它可以让你一次性看到所有局部变量的值,有助于检查变量的初始化和赋值是否正确。
- 调试函数内部逻辑:在调试函数时,通过局部变量窗口可以清晰地跟踪函数内部变量的变化,从而更好地理解函数的执行过程和逻辑。
两者的区别与联系
- 区别:自动变量窗口主要关注当前执行语句上下文相关的变量,范围相对较窄且动态变化;而局部变量窗口显示的是当前作用域内的所有局部变量,范围更全面。
- 联系:在很多情况下,自动变量窗口显示的变量会包含在局部变量窗口中,它们都是为了帮助开发者在调试过程中查看变量的值,辅助定位和解决问题。
即时窗口(Immediate Window)
功能概述
即时窗口允许开发者在调试程序时,直接输入并执行代码表达式或命令。它为开发者提供了一个与正在调试的程序进行交互的环境,能实时查看表达式的计算结果、调用函数、修改变量值等,帮助开发者更高效地调试和验证代码逻辑。
打开方式
在 Visual Studio 中,你可以通过以下方式打开即时窗口:
- 菜单操作:选择 “调试” -> “窗口” -> “即时”。
- 快捷键:使用快捷键
Ctrl + Alt + I
快速打开。
使用方法
1. 计算表达式
在即时窗口中,你可以输入各种有效的代码表达式,按下回车键后,IDE 会立即计算并显示该表达式的结果。例如,在调试 C# 程序时,若程序中有变量 int a = 5; int b = 3;
,你可以在即时窗口中输入 a + b
,按下回车键,窗口会显示计算结果 8
。
2. 调用函数
你可以直接在即时窗口中调用程序中的函数,并查看函数的返回值。假设程序中有一个计算两个数之和的函数 public int Add(int x, int y) { return x + y; }
,在即时窗口中输入 Add(2, 3)
,按下回车键,会显示函数的返回值 5
。
3. 修改变量值
在调试暂停状态下,你可以在即时窗口中修改变量的值。例如,若有变量 int num = 10;
,在即时窗口中输入 num = 20
,按下回车键,变量 num
的值就会被修改为 20
,后续程序的执行会基于新的值进行。
4. 执行命令
即时窗口还支持执行一些特定的调试命令。例如,在 Visual Studio 中,你可以使用 ?
来计算表达式的值,输入 ? a * b
与直接输入 a * b
的效果相同。
应用场景
调试复杂逻辑
当程序中存在复杂的计算逻辑或条件判断时,你可以在即时窗口中输入相关表达式,逐步验证每一步的计算结果,帮助你理解和排查逻辑错误。
动态测试代码
在调试过程中,你可以临时修改变量的值或调用不同的函数,动态测试程序在不同输入下的行为,而无需修改源代码并重新编译运行。
调用栈(Call Stack)
定义
调用栈是一种数据结构,用于跟踪程序中函数调用的顺序。它记录了每个正在执行的函数的相关信息,包括函数的返回地址、局部变量以及参数等,确保程序在函数调用结束后能够正确返回到调用点继续执行。
工作原理
- 函数调用:当程序执行到一个函数调用语句时,系统会将当前的执行上下文(包括返回地址、局部变量等)压入调用栈的栈顶,然后跳转到被调用函数的起始位置开始执行。
- 函数返回:当被调用函数执行完毕后,系统会从调用栈的栈顶弹出该函数的执行上下文,恢复之前的执行环境,并根据返回地址返回到调用点继续执行后续代码。
在调试中的应用
- 查看调用栈信息:在调试工具(如 Visual Studio、PyCharm 等)中,通常可以查看当前的调用栈信息。当程序暂停在断点处或出现异常时,调试工具会显示调用栈的内容,包括当前正在执行的函数以及调用它的函数,帮助开发者了解程序的执行流程。
- 定位错误:当程序出现错误时,调用栈可以显示错误发生的具体位置和函数调用路径。通过分析调用栈,开发者可以快速定位到错误所在的函数,进而排查问题。
- 回溯调用过程:在调试复杂的程序时,开发者可以利用调用栈回溯函数的调用过程,了解每个函数的输入和输出,从而更好地理解程序的运行逻辑。
简单用一个递归函数来使用调用栈来观察程序运行情况
void print(size_t n)
{if (n == 0) return;std::cout << n << " ";print(n - 1);
}
练习(找出错误)
1,二分查找
陷入死循环,当在数组里找不到目标时。
错误代码
int binary_search(int arr[], int size, int target)
{int left = 0, right = size - 1;int mid;while (left <= right){mid = (left + right) / 2;if (arr[mid] == target)return mid;else if (arr[mid] < target)left = mid;elseright = mid;}return -1;
}int main()
{int arr[] = { 2, 3, 4, 5,6,7,10, 40,59,60 };int n = sizeof(arr) / sizeof(arr[0]);int target = 8;int result = binary_search(arr, n, target);if (result == -1)cout << "Element not present in array";elsecout << "Element is present at index " << result;return 0;
}
你一看旁边的DataTips,一下子就知道。
注:这里为什么看到不到arr里的元素,因为函数传参时会发生类型衰变,数组指针会变成普通指针。
2,链表链接
代码
namespace MyNamespace
{struct Node{int data;Node* next;Node(int x) : data(x), next(NULL) {}};void printList(Node* head){while (head != NULL){cout << head->data << " ";head = head->next;}cout << endl;}Node* createList(int arr[], int n){Node* head = new Node(arr[0]);Node* tail = head;for (int i = 1; i < n; i++){tail->next = new Node(arr[i]);}return head;}}
int main()
{int arr[] = { 1, 2, 3, 4, 5 };int n = sizeof(arr) / sizeof(arr[0]);MyNamespace::Node* head = MyNamespace::createList(arr, n);MyNamespace::printList(head);return 0;
}
可以看到输出结果只有1,5.
所以与其盯着代码进行头脑风暴,一遍遍在大脑里演算还不如直接调试,观察程序究竟是怎么跑的,减轻大脑负担。
相关文章:
Visual Studio里的调试(debugging)功能介绍
参考 1- Introduction to Debugging | Basic Visual Studio Debugging(这是一位印度博主视频,我下面做到笔记也主要参考她的视频,但不得不说口音太重了,一股咖喱味) 目录 个人对调试浅显的认识和对调试的介绍逐行调…...
10.2linux内核定时器实验(详细编程)_csdn
我尽量讲的更详细,为了关注我的粉丝!!! 本章使用通过设置一个定时器来实现周期性的闪烁 LED 灯,因此本章例程就使用到了一个LED 灯。 这里我们以毫秒为单位,所以要用msecs_to_jiffies这个函数。 如果是2s就…...
机器学习——正则化、欠拟合、过拟合、学习曲线
过拟合(overfitting):模型只能拟合训练数据的状态。即过度训练。 避免过拟合的几种方法: ①增加全部训练数据的数量(最为有效的方式) ②使用简单的模型(简单的模型学不够,复杂的模型学的太多&am…...
Java多线程与高并发专题——阻塞和非阻塞队列的并发安全原理是什么?
引入 之前我们探究了常见的阻塞队列的特点,在本文我们就以 ArrayBlockingQueue 为例,首先分析 BlockingQueue ,也就是阻塞队列的线程安全原理,然后再看看它的兄弟——非阻塞队列的并发安全原理。 ArrayBlockingQueue 源码分析 …...
git 撤销某次提交的上交到远程服务器的commit提交,此提交后面的commit需要保留【deeepseek生成】
核心思路 使用 git rebase -i 重写提交历史,删除目标提交后强制推送到远程(需谨慎操作)。 操作步骤 1. 确认要删除的提交位置 # 查看提交历史(找到要删除的提交哈希,例如 a1b2c3d) git log --oneline查看提…...
docker composeyaml文件,什么是swap-space,内存不足硬盘来凑,--ipc=host,yaml文件、环境变量、容器报警健康检查
--swap-space 参数明确针对的是系统内存(RAM),与显存(GPU Memory)无关。以下是关键区分: 内存(RAM) vs 显存(GPU Memory) 类型内存(RAMÿ…...
tsfresh:时间序列特征自动提取与应用
tsfresh:时间序列特征自动提取与应用 本文系统介绍了 tsfresh 技术在 A 股市场数据分析与量化投资中的应用。从基础特征提取到高级策略开发,结合实战案例,详细讲解了如何利用 tsfresh 构建量化投资策略,并优化风险控制,…...
【A2DP】深入解读A2DP中通用访问配置文件(GAP)的互操作性要求
目录 一、模式支持要求 1.1 发现模式 1.2 连接模式 1.3 绑定模式 1.4 模式间依赖关系总结 1.5 注意事项 1.6 协议设计深层逻辑 二、安全机制(Security Aspects) 三、空闲模式操作(Idle Mode Procedures) 3.1 支持要求 …...
CUDA编程之内存
CUDA的内存类型有全局内存、共享内存、常量内存、纹理内存、本地内存、寄存器等。我们需要分别了解它们的特点和使用场景。在CUDA编程中,合理利用各种内存类型对性能优化至关重要。 1. 全局内存(Global Memory) 特点:设…...
【Agent实战】货物上架位置推荐助手(RAG方式+结构化prompt(CoT)+API工具结合ChatGPT4o能力Agent项目实践)
本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 结论 效果图示 1.prompt 2. API工具封…...
ffmpeg面试题整理
1. 基础概念 问题:FFmpeg 是什么?它的核心功能有哪些? 编解码:支持几乎所有音视频格式(如 H.264, AAC, MP3)。转换:在不同容器格式之间转换(如 MP4 → MKV)。流处理&…...
Idea运行项目报错:java.lang.OutOfMemoryError: Java heap space 解决方法
问题描述 Maven构建的时候,一直报错java.lang.OutOfMemoryError: Java heap space 尝试解决 找了几个JAVA高级小伙伴,一起去百度了各种可能,设置内存大小,发现都不行,还不断的重装了IDEA,以为是这个版本…...
解决 Linux /dev/mapper/ubuntu--vg-ubuntu--lv 磁盘空间不足的问题
解决 Linux /dev/mapper/ubuntu–vg-ubuntu–lv 磁盘空间不足的问题 https://blog.csdn.net/weixin_47908992/article/details/139882219 查看LVM卷组的信息 vgdisplay rootubuntu:~# vgdisplay--- Volume group ---VG Name ubuntu-vgSystem ID Fo…...
前端UI编程基础知识:基础三要素(结构→表现→行为)
以下是重新梳理的前端UI编程基础知识体系,结合最新技术趋势与实战要点,以更适合快速掌握的逻辑结构呈现: 一、基础三要素(结构→表现→行为) 1. HTML5 核心能力 • 语义化标签:<header>, <nav&g…...
Trae:与AI结伴,开启编程新体验
Trae:与AI结伴,开启编程新体验 在数字化时代,编程已经成为推动技术发展的核心力量。然而,随着项目复杂度的增加,开发者面临着诸多挑战,例如代码编写效率低下、代码质量难以把控等。如今,Trae作…...
如何用正则表达式爬取古诗文网中的数据(python爬虫)
一、了解正则表达式的基本内容: 什么是正则表达式 正则表达式(Regular Expression,简称 regex)是一种用于匹配字符串的模式。它通过特定的语法规则,可以高效地搜索、替换和提取文本中的特定内容。正则表达式广泛应用于…...
深度学习 Deep Learning 第1章 深度学习简介
第1章 深度学习简介 概述 本章介绍人工智能(AI)和深度学习领域,讨论其历史发展、关键概念和应用。解释深度学习如何从早期的AI和机器学习方法演变而来,以及如何有效解决之前方法无法应对的挑战。 关键概念 1. 人工智能的演变 …...
ByteByteGo学习笔记:通知系统设计
引言 在当今这个信息爆炸的时代,通知系统已经成为了现代应用程序中不可或缺的重要组成部分。无论是突发新闻的即时推送、产品更新的及时告知、促销活动的精准触达,还是用户交互的实时反馈,通知都扮演着至关重要的角色。一个高效、可靠、可扩…...
[设计模式]1_设计模式概览
摘要:设计模式原则、设计模式的划分与简要概括,怎么使用重构获得设计模式并改善代码的坏味道。 本篇作概览与检索用,后续结合源码进行具体模式深入学习。 目录 1、设计模式原理 核心原则(语言无关) 本质原理图 原…...
Python + Qt Designer构建多界面GUI应用程序:Python如何调用多个界面文件
引言 Qt Designer是一个用户友好的图形用户界面设计工具,它可以帮助开发人员通过拖放的方式快速创建界面。在实际开发中,往往需要设计多个界面文件,并在Python代码中进行统一管理和使用。本文将介绍如何在Python中使用Qt Designer设计好的多…...
AGI大模型(7):提示词应用
1 生成数据 LLM具有⽣成连贯⽂本的强⼤能⼒。使⽤有效的提示策略可以引导模型产⽣更好、更⼀致和更真实的响应。LLMs还可以特别有⽤地⽣成数据,这对于运⾏各种实验和评估⾮常有⽤。例如,我们可以使⽤它来为情感分类器⽣成快速样本,如下所示: 提示: ⽣成10个情感分析的范…...
【倒霉bug2025】找不到vc_runtimeMinimum_x64.msi
今天是倒霉的一天,当喉咙痛到无法出门玩耍的我打开steam准备开始玩《冰封世界》时,游戏启动直接报错 在选择安装之后弹出一个经典窗口 然后在C:\ProgramData\PackageCache中找msi到位置点击确定继续报错说msi版本不对 上网一搜,找不到vc_ru…...
什么是强哈希算法pbkdf2(Password-Based Key Derivation Function)
文章目录 什么是pbkdf2使用场景 在线工具 什么是pbkdf2 维基百科:https://zh.wikipedia.org/zh-cn/PBKDF2 PBKDF2(Password-Based Key Derivation Function 2)是一种基于密码的密钥派生函数。它的主要作用是从密码和盐(salt&…...
Python 基础语法详解
一、变量和数据类型 变量 在 Python 中,变量无需声明类型,直接赋值即可。变量名区分大小写。 # 整数类型 age 25 print(age) # 输出:25# 浮点数类型 height 1.75 print(height) # 输出:1.75# 字符串类型 name "张三&…...
AI Agent 时代开幕-Manus AI与OpenAI Agent SDK掀起新风暴
【本周AI新闻: AI Agent 时代开幕-Manus AI与OpenAI Agent SDK掀起新风暴】 https://www.bilibili.com/video/BV1bkQyYCEvQ/?share_sourcecopy_web&vd_source32ed33e1165d68429b2e2eb4749f3f26 最近AI圈子里最火的话题非Manus莫属!这款由中国武汉创业公司“蝴…...
为什么会出现redis数据库?redis是什么?
什么是 Redis? 为什么要用 Redis? 下面我将从 Redis 出现的背景、Redis 的解决方案个来回答。 1、Redis 出现的背景 互联网的应用越来越多,例如社交网络、电商、实时服务发展的十分迅速,这就导致了传统技术栈(如关系型数据库)…...
每日一题---dd爱框框(Java中输入数据过多)
dd爱框框 实例: 输入: 10 20 1 1 6 10 9 3 3 5 3 7 输出: 3 5 这道题要解决Java中输入的数过多时,时间不足的的问题。 应用这个输入模板即可解决: Java中输入大量数据 import java.util.*; import java.io.*;pu…...
Flink-学习路线
最近想学习一下Flink,公司的实时需求还是不少的,因此结合ai整理了一份学习路线,记录一下。 当然,公司也有Scala版本Flink框架,也学习了一下。这里只说Java版本 1. Java基础 目标: 掌握Java编程语言的基础知识。 内容…...
一次Milvus迁移的记录
前言 希望把Linux上生产环境中使用docker compose运行的milvus迁移到本地(mac os)的docker compose中 操作过程 找到了官方有两个相关的项目: https://github.com/zilliztech/milvus-backup https://github.com/zilliztech/vts 但是…我都没用,因为使…...
矩阵的转置
对于的矩阵,使用两个指针变量,可以方便实现(i,j)处元素与(j,i)处元素交换位置。令指针Arow&A[i][0],则Arow[j]可实现对第i行j列元素的访问。令指针Bptr&A[0][i],则*Bptr就可以访问(0,i)处元素,然后,…...
使用 VLOOKUP 和条件格式在 Excel 中查找并标红匹配的串号
使用 VLOOKUP 和条件格式在 Excel 中查找并标红匹配的串号 你的步骤非常详细且清晰,能够帮助用户在 Excel 中通过 VLOOKUP 和条件格式来查找并标红匹配的串号。以下是对你提供的步骤的简要总结和补充说明: 1. 添加“是否匹配”列 在 a.xlsx 中新增一列…...
Python Matplotlib面试题精选及参考答案
目录 绘制函数 y=2x+5 在区间 [1,10] 的折线图,设置标题和坐标轴标签 在同一图中绘制 sin (x) 和 cos (x) 曲线,添加图例和网格线(x∈[0,2π]) 绘制分段函数:当 x<0 时 y=0,x≥0 时 y=x,设置不同线段颜色 绘制带数据点的折线图,使用红色虚线样式和圆形标记(数据…...
在线 SQL 转 SQLAlchemy:一键生成 Python 数据模型
一款高效的在线 SQL 转 SQLAlchemy 工具,支持自动解析 SQL 语句并生成 Python SQLAlchemy 模型代码,适用于数据库管理、后端开发和 ORM 结构映射。无需手写 SQLAlchemy 模型,一键转换 SQL 结构,提升开发效率,简化数据库…...
基于自定义线程池手写一个异步任务管理器
我们在后端执行某些耗时逻辑操作时往往会导致长时间的线程阻塞,在这种情况之下,我们往往会引一条异步线程去处理这些异步任务,如果每次都创建新的线程来处理这些任务,不仅会增加代码冗余,还可能造成线程管理混乱&#…...
基恩士PLC编程小技巧八:脚本过长如何实现换行及替换
基恩士PLC编程小技巧八:脚本过长如何实现换行? 一、问题点 我们在使用基恩士编程软件KV STUDIO 进行脚本编程时,经常遇到这样的问题:脚本的一行过长,程序不好阅读及维护。 IF MR1000 OR MR1001 OR MR1002 OR MR1003 OR…...
每日一题---数组中两个字符串的最小距离
数组中两个字符串的最小距离 给定一个字符串数组strs,再给定两个字符串str1和str2,返回在strs中str1和str2的最小距离,如果str1或str2为null,或不在strs中,返回-1。 链接:数组中两个字符串的最小距离__牛…...
【PTA题目解答】7-1利用STL比较数据大小并排序(15分)c++
1.题目: 2.算法原理 根据题目要求,模拟即可,set容器会帮我们把插入的数自动排序好 题目说输入非整型数据就停止,不用特意判断输入的数据是整型还是非整型,如果用户输入的是字符(例如 a)&#…...
如何用Deepseek制作流程图?
使用Deepseek制作流程图,本质上是让AI根据你的需求,生成相关流程图的代码,然后在流程图编辑器中渲染,类似于Python一样,ChatGPT可以生成代码,但仍需在IDE中执行。 你知道绘制流程图最高效的工具是什么吗&a…...
【09】单片机编程核心技巧:变量赋值,从定义到存储的底层逻辑
【09】单片机编程核心技巧:变量赋值,从定义到存储的底层逻辑 🌟 核心概念 单片机变量的定义与赋值是程序设计的基础,其本质是通过 RAM(随机存储器) 和 ROM(只读存储器) 的协作实现…...
vscode python相对路径的问题
vscode python相对路径的问题 最近使用使用vscode连接wsl2写python时,经常遇到找不到包中的方法的问题,最终发现vscode在执行python代码时目录不是从当前python文件开始算起,而是从当前工作区的目录开始算起,比如说我打开的是/ho…...
C语言中的指针与函数
引言 在C语言编程中,指针是一个非常重要且强大的概念。它不仅帮助我们高效地管理内存,还能提升程序的灵活性和性能。而指针与函数的结合使用,是C语言中非常常见且极具挑战性的一个话题。正确理解和使用指针与函数的关系,不仅能帮助程序员提高代码质量,还能优化程序的执行…...
深度学习-服务器训练SparseDrive过程记录
1、cuda安装 1.1 卸载安装失败的cuda 参考:https://blog.csdn.net/weixin_40826634/article/details/127493809 注意:因为/usr/local/cuda-xx.x/bin/下没有卸载脚本,很可能是apt安装的,所以通过执行下面的命令删除: a…...
理解langchain langgraph 官方文档示例代码中的MemorySaver
以下是langchain v0.3官方示例代码 from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import START, MessagesState, StateGraph# 可以理解为:定义一个流程,这个流程中用到的数据类型是Messages。 <---定义一个有向图&…...
JumpServer基础功能介绍演示
堡垒机可以让运维人员通过统一的平台对设备进行维护,集中的进行权限的管理,同时也会对每个操作进行记录,方便后期的溯源和审查,JumpServer是由飞致云推出的开源堡垒机,通过简单的安装配置即可投入使用,本文…...
Spring @Bean注解使用场景二
bean:最近在写一篇让Successfactors顾问都能搞明白的sso的逻辑的文章,所以一致在研究IAS的saml2.0的协议,希望用代码去解释SP、idp的一些概念,让顾问了解SSO与saml的关系,在github找代码的时候发现一些代码的调用关系很难理解&…...
创业者认知、思辨、成长指南
一、为什么要创业? 1、因为没有家产继承和家庭关系,不能躺平; 比如父母留下了大量的财富,靠钱生钱吃利息,收租,做做投资这些形式,就可以活得很好; 再比如父母或者血亲有资源&#…...
ECharts中Map(地图)样式配置、渐变色生成
前言 ECharts是我们常用的图表控件,功能特别强大,每次使用都要查API比较繁琐,这里就记录开发中常用的配置。 官网:https://echarts.apache.org/handbook/zh/get-started 配置项:https://echarts.apache.org/zh/opti…...
PostgreSQL存储管理体系结构学习笔记2
1.表和元组的组织方式 在PostgreSQL中,同一个表中的元组按照创建顺序依次插入到表文件中。元组之间不进行关联,这样的表文件称之为堆文件。PostgreSQL系统中包含了四种堆文件:普通堆,临时堆,序列,TOAST表。…...
【PTA题目解答】7-3 字符串的全排列(20分)next_permutation
1.题目 给定一个全由小写字母构成的字符串,求它的全排列,按照字典序从小到大输出。 输入格式: 一行,一个字符串,长度不大于8。 输出格式: 输出所有全排列,每行一种排列形式,字典序从小到大。 输入样例…...
SOME/IP:用Python实现协议订阅、Offer、订阅ACK与报文接收
文章目录 前言一、代码层次二、详细代码1. eth_scapy_sd.py2、eth_scapy_someip.py3、network_define.py4、packet_define.py5、unpack_define.py6、someip_controller.py 前言 1、需要pip安装scapy库 2、需要修改根据实际情况配置network_define.py 3、执行someip_controller…...