GDB调试简单介绍

news2024/11/14 0:05:26

最近和许多同事交流时,发现好多人只是在IDE上debug,但是gdb却一点都不了解;校招新来的同事更是都没听过gdb这个工具,所以在培训时给他们培训了一下;另外好久也没写blog了,刚好把这篇笔记简单分享一下。

0 简介

GDB 全称“GNU symbolic debugger”,GNU家族成员之一。
所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。

也就是说,通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码;在我们日常coding debug时,有时很难肉眼发现自己写的代码的问题之处,这时GDB就排上用场了。

下载和安装这里不做说明,放一个源码链接:http://ftp.gnu.org/gnu/gdb/,感兴趣的小伙伴,可以下载看下。

1 常用debug命令

这里仅介绍些基础命令,覆盖日常50-60%的使用,简单调试够用了,实际工程使用中肯定是不够的,先掌握基本的命令,遇到问题再具体学习(有时间精力的小伙伴也可以找大全命令学习下)。

1.1 汇总

GDB 的主要功能就是监控程序的执行流程。这也就意味着,只有当源程序文件编译为可执行文件并执行时,并且该文件中必须包含必要的调试信息(比如各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等),GDB才会派上用场。

所以在编译时需要使用 gcc/g++ -g 选项编译源文件,才可生成满足 GDB 要求的可执行文件
命令简介

1.2启动程序

根据不同场景的需要,GDB 调试器提供了多种方式来启动目标程序,其中最常用的就是run 指令,其次为 start 指令。也就是说,run和 start 指令都可以用来在 GDB 调试器中启动程序,它们之间的区别是:

默认情况下:
run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run指令会执行程序至第一个断点处;

start 指令会执行程序至main()主函数的起始位置,即在main()函数的第一行语句处停止执行(该行代码尚未执行)。

1.3 break命令

break 命令(可以用b 代替)常用的语法格式有以下 2 种。

1(gdb) break location      // b location
2(gdb) break ... if cond   // b .. if cond

其中,第一种格式中,location 用于指定打断点的具体位置,其表示方式有多种,如表 1 所示。
在这里插入图片描述
第二种格式中,… 可以是表 1 中所有参数的值,用于指定打断点的具体位置;cond 为某个表达式。整体的含义为:每次程序执行到 … 位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。另外也可以用condition 为断点设置命中条件。

1.4 删除或禁用断点

1.4.1删除断点

如果之前建立的断点不再需要或者暂时不需要,该如何删除或者禁用呢?常用的方式有 2 种:

使用 quit 命令退出调试,然后重新对目标程序启动调试,此方法会将消除上一次调试操作中建立的所有断点;
使用专门删除或禁用断点的命令,既可以删除某一个断点,也可以删除全部断点。
无论是普通断点、观察断点还是捕捉断点,都可以使用 clear 或者 delete 命令进行删除。

clear 命令可以删除指定位置处的所有断点,常用的语法格式如下所示:

(gdb) clear location

参数location 通常为某一行代码的行号或者某个具体的函数名。当 location 参数为某个函数的函数名时,表示删除位于该函数入口处的所有断点。

delete 命令(可以缩写为 d)通常用来删除所有断点,也可以删除指定编号的各类型断点,语法格式如下:

delete [breakpoints] [num]

其中,breakpoints 参数可有可无,num 参数为指定断点的编号,其可以是delete 删除某一个断点,而非全部。

如果不指定 num参数,则 delete 命令会删除当前程序中存在的所有断点。

1.4.2禁用端点

禁用断点可以使用 disable 命令,语法格式如下:

disable [breakpoints] [num...]

breakpoints 参数可有可无;num…表示可以有多个参数,每个参数都为要禁用断点的编号。如果指定 num…,disable 命令会禁用指定编号的断点;反之若不设定 num…,则 disable 会禁用当前程序中所有的断点。

对于禁用的断点,可以使用enable 命令激活,该命令的语法格式有多种,分别对应有不同的功能:

  • 1 enable [breakpoints] [num…] 激活用 num… 参数指定的多个断点,如果不设定 num…,表示激活所有禁用的断点
  • 2 enable [breakpoints] once num… 临时激活以 num… 为编号的多个断点,但断点只能使用 1 次,之后会自动回到禁用状态
  • 3 enable [breakpoints] count num… 临时激活以 num… 为编号的多个断点,断点可以使用 count 次,之后进入禁用状态
  • 4 enable [breakpoints] delete num… 激活 num… 为编号的多个断点,但断点只能使用 1 次,之后会被永久删除。

1.5查看变量或表达式的值

对于在调试期间查看某个变量或表达式的值,GDB 调试器提供有 2 种方法,即使用 print 命令或者 display命令。

1.5.1print

它的功能就是在 GDB 调试程序的过程中,输出或者修改指定变量或者表达式的值。

print 命令可以缩写为 p,最常用的语法格式如下所示:

(gdb) print num
(gdb) p num

其中,参数 num 用来代指要查看或者修改的目标变量或者表达式。

当程序中包含多个作用域不同但名称相同的变量或表达式时,可以借助::运算符明确指定要查看的目标变量或表达式。::运算符的语法格式如下:

1(gdb) print file::variable
2(gdb) print function::variable

其中 file用于指定具体的文件名,funciton 用于指定具体所在函数的函数名,variable表示要查看的目标变量或表达式。

另外,print也可以打印出类或者结构体变量的值。

1.5.2 display

和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。

也就是说,使用 1 次 print 命令只能查看 1 次某个变量或表达式的值,而同样使用 1 次 display 命令,每次程序暂停执行时都会自动打印出目标变量或表达式的值。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸。

display 命令没有缩写形式,常用的语法格式如下 2 种:

(gdb) display expr
(gdb) display/fmt expr

1.6 GDB单步调试

根据实际场景的需要,GDB 调试器共提供了 3 种可实现单步调试程序的方法,即使用 next、step 和 until 命令。换句话说,这 3 个命令都可以控制 GDB调试器每次仅执行 1 行代码,但除此之外,它们各自还有不同的功能。

1.6.1 next命令

next 是最常用来进行单步调试的命令,其最大的特点是当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next 指令都会一步执行完。也就是说,对于调用的函数来说,next 命令只会将其视作一行代码。

next 命令可以缩写为n 命令,使用方法也很简单,语法格式如下:

(gdb) next count

1.6.2 step命令
通常情况下,step 命令和next命令的功能相同,都是单步执行程序。不同之处在于,当step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行。

step 命令可以缩写为 s命令,用法和 next 命令相同,语法格式如下:

(gdb) step count

1.6.3 until命令
until 命令可以简写为 u 命令,有 2 种语法格式,如下所示:

1(gdb) until
2(gdb) until location

其中,参数 location为某一行代码的行号。

不带参数的 until命令,可以使 GDB调试器快速运行完当前的循环体,并运行至循环体外停止。注意,until 命令并非任何情况下都会发挥这个作用,只有当执行至循环体尾部(最后一行代码)时,until命令才会发生此作用;反之,until命令和 next 命令的功能一样,只是单步执行程序。

1.6.4 return命令

实际调试时,在某个函数中调试一段时间后,可能不需要再一步步执行到函数返回处,希望直接执行完当前函数,这时可以使用 finish命令。与finish 命令类似的还有 return 命令,它们都可以结束当前执行的函数。

1.6.5 finish命令

finish 命令和 return命令的区别是,finish命令会执行函数到正常退出;而 return 命令是立即结束执行当前函数并返回,也就是说,如果当前函数还有剩余的代码未执行完毕,也不会执行了。除此之外,return命令还有一个功能,即可以指定该函数的返回值。

1.6.6 jump命令

jump 命令的功能是直接跳到指定行继续执行程序,其语法格式为:

(gdb) jump location

其中,location 通常为某一行代码的行号。

也就是说,jump 命令可以略过某些代码,直接跳到 location处的代码继续执行程序。这意味着,如果你跳过了某个变量(对象)的初始化代码,直接执行操作该变量(对象)的代码,很可能会导致程序崩溃或出现其它 Bug。另外,如果 jump跳转到的位置后续没有断点,那么 GDB会直接执行自跳转处开始的后续代码。

1.7 GDB search 命令

调试文件时,某些时候可能会去找寻找某一行或者是某一部分的代码。可以使用 list 显示全部的源码,然后进行查看。当源文件的代码量较少时,我们可以使用这种方式搜索。如果源文件的代码量很大,使用这种方式寻找效率会很低。所以 GDB中提供了相关的源代码搜索的的search命令。

search 命令的语法格式为:

search <regexp>
reverse-search <regexp>

第一项命令格式表示从当前行的开始向前搜索,后一项表示从当前行开始向后搜索。其中regexp 就是正则表达式,正则表达式描述了一种字符串匹配的模式,可以用来检查一个串中是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串。很多的编程语言都支持使用正则表达式。

1.8 查看堆栈信息

1.8.1 backtrace 命令 (bt)

backtrace 命令用于打印当前调试环境中所有栈帧的信息,常用的语法格式如下:

(gdb) backtrace [-full] [n]

其中,用 [ ] 括起来的参数为可选项,它们的含义分别为:

  • n:一个整数值,当为正整数时,表示打印最里层的 n 个栈帧的信息;n为负整数时,那么表示打印最外层n个栈帧的信息;

  • -full:打印栈帧信息的同时,打印出局部变量的值。

注意,当调试多线程程序时,该命令仅用于打印当前线程中所有栈帧的信息。如果想要打印所有线程的栈帧信息,应执行thread apply all backtrace命令。

1.8.2 frame 命令

frame命令的常用形式有 2 个:

根据栈帧编号或者栈帧地址,选定要查看的栈帧,语法格式如下:

(gdb) frame spec

该命令可以将 spec 参数指定的栈帧选定为当前栈帧。spec 参数的值,常用的指定方法有 3 种:

通过栈帧的编号指定。0 为当前被调用函数对应的栈帧号,最大编号的栈帧对应的函数通常就是 main() 主函数;
借助栈帧的地址指定。栈帧地址可以通过 info frame 命令(后续会讲)打印出的信息中看到;
通过函数的函数名指定。注意,如果是类似递归函数,其对应多个栈帧的话,通过此方法指定的是编号最小的那个栈帧。
除此之外,对于选定一个栈帧作为当前栈帧,GDB 调试器还提供有up 和down两个命令。其中,up命令的语法格式为:

(gdb) up n

其中 n为整数,默认值为 1。该命令表示在当前栈帧编号(假设为 m)的基础上,选定 m+n为编号的栈帧作为新的当前栈帧。

相对地,down 命令的语法格式为:

(gdb) down n

其中n为整数,默认值为 1。该命令表示在当前栈帧编号(假设为 m)的基础上,选定m-n 为编号的栈帧作为新的当前栈帧。

借助如下命令,我们可以查看当前栈帧中存储的信息:

(gdb) info frame

该命令会依次打印出当前栈帧的如下信息:

• 当前栈帧的编号,以及栈帧的地址;
• 当前栈帧对应函数的存储地址,以及该函数被调用时的代码存储的地址
• 当前函数的调用者,对应的栈帧的地址;
• 编写此栈帧所用的编程语言;
• 函数参数的存储地址以及值;
• 函数中局部变量的存储地址;
• 栈帧中存储的寄存器变量,例如指令寄存器(64位环境中用 rip 表示,32为环境中用eip 表示)、堆栈基指针寄存器(64位环境用 rbp表示,32位环境用 ebp表示)等。

除此之外,还可以使用info args命令查看当前函数各个参数的值;使用info locals命令查看当前函数中各局部变量的值。

2 读写内存寄存器

调试过程中,要经常查看或者改写内存、寄存器的值,操作如下:

2.1 读取

  • 读取某个变量的值: p <var>
  • 读取某个内存地址里的内容: x <memaddr>
  • 读取某个寄存器的值: info register

后面操作都以下面程序为例:

int main(void)
{
    unsigned int *src base addr = (unsigned int *)0x1c000292;
    srand(__get_rv_cycle() | get_rv_instret() | __RV_CSR_READ(CSR MCYCLE));
    //*src base addr = 0x7788;
    int a = 5;
    uint32 t rval = rand();
    uint32 t hartid = __RV_CSR_READ(CSR_MHARTID);
    rv_csr_t misa = __RV_CSR_READ(CSR_MISA);
   
    printf("Hart %d, MISA: 0x%lx\r\n", hartid, misa);
    print_misa();
    for (int i = e; i < RUN_LOOPS; i ++) {
        printf("%d: Hello World From Nuclei RISC-V Processor! rin",i)
    }
    simulation pass();
}

2.1.1 读取变量值

在这里插入图片描述

  • p/x:其中p为print,x代表16进制

2.1.2 读取内存

在这里插入图片描述

  • 其中x代表examine,检查可以直接查看内存地址
  • 也可以通过print打印该地址的解引用值

另外,使用x命令打印多条内存数据的格式为x/nfu addr。其中:

  • n表示输出单元的个数;
  • f表示输出格式,比如x是以16进制形式输出,o是以8进制形式输出;
  • u表示一个单元的长度,b是一个byte,h是两个byte(halfword),w是四个byte(word),g是八个byte(giant word)。

2.1.3 查看寄存器信息

这里说的寄存器是通用寄存器,不是某个ip的寄存器(和内存一样操作)
在这里插入图片描述

2.2 写操作

写操作一般用的不多,但最好还是了解。

2.2.1 修改变量的值

set var <name> = <value>

在这里插入图片描述

2.2.2修改寄存器的值

set $<register> = <value>

修改通用寄存器
在这里插入图片描述

2.2.3修改pc值

在这里插入图片描述

2.2.4修改内存值

在这里插入图片描述

3 watchpoint使用

很多情况下,程序的bug是由于某个变量或地址被莫名修改而导致的,但是具体什么时候修改了该值,我们很难定位到。

和breakpoint类似,watchpoint用来观察数据或者地址变化,breakpoint是指令断点;观察点watchpoint功能,可以监控程序中变量或表达式的值,只要在运行过程中发生改变,程序就会停止执行。可以说学会watchpoint,能够实现让bug自动现身的效果。

3.1适用场景

  • 数据污染,变量异常变化导致bug
  • 内存泄漏,踩了地址
  • 确定了某个异常变量,但是该变量被多次使用、还会在各种循环内被操作。
  • 多线程场景,线程切来切去,不知道变量具体被哪个线程修改了。

3.2 watchpoint命令

在这里插入图片描述

3.3 使用演示

错误dump如下:
在这里插入图片描述

  • 1)打印出现指令异常。当前PC值为0x1c000294
  • 2)异常错误MCAUSE 是 2,非法指令
  • 3)猜测应该是程序哪里修改了指令(可能是内存踩踏、内存泄漏),导致0x1c000294处的指令被修改为非法指令0x00
int main(void)
{
    unsigned int *src base addr = (unsigned int *)0x1c000292;
    srand(__get_rv_cycle() | get_rv_instret() | __RV_CSR_READ(CSR MCYCLE));
    //*src base addr = 0x7788;
    int a = 5;
    uint32 t rval = rand();
    uint32 t hartid = __RV_CSR_READ(CSR_MHARTID);
    rv_csr_t misa = __RV_CSR_READ(CSR_MISA);
   
    printf("Hart %d, MISA: 0x%lx\r\n", hartid, misa);
    print_misa();
    for (int i = e; i < RUN_LOOPS; i ++) {
        printf("%d: Hello World From Nuclei RISC-V Processor! rin",i)
    }
    simulation pass();
}
  • 4)实际是刻意为之,修改了指令内存地址
  • 5)实际情况下是,我们不知道哪里出了问题,这时就已使用watchpoint来找出问题
    在这里插入图片描述
  • 6)当观察点的内存内容被修改时,cpu将会被hang住,通过查看上下文锁定位置这里在99行(还未执行)上一句被修改

简单介绍了下gdb的基本使用,内存读写命令和watchpoint,抛砖引玉吧,如有错误之处请在评论区指出。

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

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

相关文章

NetSuite SuiteWorld 2023观后感

25年&#xff01;本周结束的SuiteWorld 2023是NetSuite的25周年庆。 下面两张照片分别来自1998年和今年。同样的人&#xff0c;同样的地点。左二是Evan&#xff0c;NetSuite的主要创始人。 当Evan展望未来25年后NetSuite这四位创始人的样子时。Evan GPT&#xff0c;给出了如下…

XIlinx提供的DDR3 IP与 UG586

DDR系统需要关注的三样东西&#xff1a;控制器、PHY、SDRAM颗粒&#xff0c;但这是实现一个DDR3 IP所需要的&#xff0c;如果只希望调用IP的话&#xff0c;则只需要调用IP即可&#xff0c;目前时间紧急&#xff0c;我先学一学如何使用IP&#xff0c;解决卡脖子的问题&#xff0…

Linux系统管理:虚拟机Kylin OS安装

目录 一、理论 1.Kylin OS 二、实验 1.虚拟机Kylin OS安装准备阶段 2.安装Kylin OS 3.进入系统 一、理论 1.Kylin OS &#xff08;1&#xff09;简介 麒麟操作系统&#xff08;Kylin OS&#xff09;亦称银河麒麟&#xff0c;是由中国国防科技大学、中软公司、联想公司…

Go-Python-Java-C-LeetCode高分解法-第十一周合集

前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接&#xff1a;LeetCode-Go-Python-Java-C 欢迎订阅CSDN专栏&#xff0c;每日一题&#xff0c;和博主一起进步 LeetCode专栏 我搜集到了50道精选题&#xff0c;适合速成概览大部分常用算法 突…

SpringBoot 入门 参数接收

接口声明 RestController //表示该类为请求处理类public class HttpDeal {RequestMapping("/login")//这个方法处理哪一个地址过来的请求public String hello(){return "返回给浏览器";}}接收参数 RequestMapping("/login")public String logi…

Spring框架(四)

1、Spring6整合JUnit 1、JUnit4 User类: package com.songzhishu.spring.bean;import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;/*** BelongsProject: Spring6* BelongsPackage: com.songzhishu.spring.bean*…

文档的重要性及接口文档模板

随着工作年限的增长&#xff0c;我们逐渐意识到工作中文档的重要性不可忽视。优质的文档不仅能提高工作效率&#xff0c;还能有效降低沟通成本&#xff0c;因此我们必须注重文档的撰写和格式。最近&#xff0c;由于未能及时更新文档&#xff0c;导致在项目开发中出现了信息冲突…

[Golang]多返回值函数、defer关键字、内置函数、变参函数、类成员函数、匿名函数

函数 文章目录 函数多返回值函数按值传递、按引用传递类成员函数改变外部变量变参函数defer和追踪说明一些常见操作实现 使用defer实现代码追踪记录函数的参数和返回值 常见的内置函数将函数作为参数闭包实例闭包将函数作为返回值 计算函数执行时间使用内存缓存来提升性能 参考…

在Lichee RV Dock上的不成功的烧录尝试

最近在学基于risc-v的简单操作系统&#xff0c;刚好手里有块Lichee RV Dock 的板子&#xff0c;所以在学了基础的"hello, world"程序后&#xff0c;想着能不能把这个程序烧录到板子上&#xff0c;简单的做个实验。 要完成这个任务&#xff0c;需要将程序烧录到sd卡上…

文献阅读:The Reversal Curse: LLMs trained on “A is B” fail to learn “B is A”

文献阅读&#xff1a;The Reversal Curse: LLMs trained on “A is B” fail to learn “B is A” 1. 文章简介2. 实验 & 结果考察 1. finetune实验2. 真实知识问答 3. 结论 & 思考 文献链接&#xff1a;https://arxiv.org/abs/2309.12288 1. 文章简介 这篇文章是前…

FPGA设计FIR滤波器低通滤波器,代码及视频

名称&#xff1a;FIR滤波器低通滤波器 软件&#xff1a;Quartus 语言&#xff1a;Verilog/VHDL 本资源含有verilog及VHDL两种语言设计的工程&#xff0c;每个工程均可实现以下FIR滤波器的功能。 代码功能&#xff1a; 设计一个8阶FIR滤波器&#xff08;低通滤波器&#xff…

使用AI编写测试用例——详细教程

随着今年chatGPT的大热&#xff0c;每个行业都试图从这项新技术当中获得一些收益我之前也写过一篇测试领域在AI技术中的探索&#xff1a;软件测试中的AI——运用AI编写测试用例现阶段AI还不能完全替代人工测试用例编写&#xff0c;但是如果把AI当做一个提高效率的工具&#xff…

关于Git的入门教程(附GitHub和Gitee的使用方法)

一. Git 概述 Git是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。Git易于学习、占地面积小、性能极快。它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性。其性能优于Subversion、CVS、Perforce和ClearCas…

基于DF模式的协作通信技术matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、DF概述 4.2、DF基本原理 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2013b 3.部分核心程序 clc; clear; close all; warning off; addpath(genpath(pwd))…

【C++】继承 ⑦ ( 继承中的对象模型分析 | 继承中的构造函数和析构函数 )

文章目录 一、继承中的对象模型分析1、继承代码示例2、基类与派生类内存模型3、问题引入 - 派生类对象构造函数和析构函数调用4、完整代码示例 - 派生类对象内存模型 二、继承中的构造函数和析构函数1、子类构造函数与析构函数调用顺序2、子类构造函数参数列表3、代码示例 - 继…

项目经理之如何组建跨部门项目团队

在跨组织、跨部门、跨专业的临时性合作项目中&#xff0c;如何组建一个高效的跨部门项目团队是确保项目成功实施的关键。本篇幅将介绍如何组建一个成功的跨部门项目团队&#xff0c;包括明确项目目标与范围、确定项目组织模型、明确角色与职责、合理划分团队结构、制定沟通机制…

app分发的一些流程

应用分发的流程通常包括以下步骤&#xff1a; 开发应用程序&#xff1a;首先&#xff0c;您需要开发您的应用程序。这包括编写代码、设计用户界面、测试应用程序等等。确保您的应用程序符合各个应用商店的规范和要求&#xff0c;以确保顺利通过审核。 准备应用材料&#xff1a…

操作系统——吸烟者问题(王道视频p34、课本ch6)

1.问题分析&#xff1a;这个问题可以看作是 可以生产多种产品的 单生产者-多消费者问题 2.代码——这里就是由于同步信号量的初值都是1&#xff0c;所以没有使用mutex互斥信号&#xff0c; 总共4个同步信号量&#xff0c;其中一个是 finish信号量

冲刺学习-MySQL-常见问题

MySQL索引的最左原则 联合索引的说明 建立三个字段的联合索引联合索引&#xff08;a&#xff0c;b&#xff0c;c&#xff09;相当于建立了索引&#xff1a;&#xff08;a&#xff09;&#xff0c;&#xff08;a&#xff0c;b&#xff09;&#xff0c;&#xff08;a&#xff0…

【力扣刷题】二叉树的中序遍历、二叉树的最大深度、翻转二叉树、对称二叉树

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 刷题篇 一、二叉树的中序遍历1.1 题目描述1…