Android 内存泄漏分析思路和案例剖析

news2024/11/26 23:49:22

分析思路

内存泄漏是指 Android 进程中,某些对象已经不再使用,但被一些生命周期更长的对象引用,导致其占用的内存资源无法被GC回收,内存占用不断增加的一种现象;内存泄漏是导致我们应用性能下降、卡顿的一种常见因素,解决此类问题最核心的思路可以总结为以下两步:

  1. 模拟内存泄漏的操作路径,观察应用 Heap 内存变化,确定出现问题的大概位置;
  2. 针对具体位置展开分析,找到泄漏对象指向 GC Root 的完整引用链,从源头治理内存泄漏。
分析工具:Android Stuido Profiler

Profiler 中常用到的内存分析的工具有两个:内存曲线图和 Heap Dump;内存曲线可以实时观察内存使用状态,协助我们进行内存的动态分析;

内存泄漏出现时,内存曲线典型的现象就是呈现阶梯状,一旦上升则难以下降;例如 Activity 泄漏后,反复打开、关闭页面内存占用会一路上升,并且点击垃圾桶图标手动GC后,占用量无法下降到打开 Activity 之前的水平,这时大概率出现内存泄漏了。

这时,我们可以手动 dump 此时刻应用堆内存中的内存分布情况,用作静态分析:

UI中的各项指标说明:

  1. Allocations:堆内存中该类的实例个数;
  2. Native Size:该类所有实例引用到的Native对象所占内存
  3. Shallow Size:该类所有实例自身的实际内存占用大小,不包括其所引用到的对象的内存占用大小;
  4. Retained Size:与 Shallow Size 不同,这个数字代表该类所有实例及其所有引用到的对象的内存占用大小;

借助一张图,可以对这几个属性有更直观的印象:

如上图,红点的内存大小代表 Shallow Size,蓝点为 Native Size,所有橙色点的内存大小则为 Retained Size;当出现内存泄漏时,我们更应该关注 Retained Size 这个数字,它的意义是,因内存泄漏导致 Java 堆内存中所浪费的内存空间大小。 因为内存泄漏往往会形成“链式效应”,从泄漏的对象出发,该对象引用的所有对象和 Native 资源都无法回收,造成内存使用效率的下降。

另外 Leaks 代表可能的内存泄漏实例数量;点击列表中的类可以查看该类的实例详情;Instance 列表中的 depth 代表该实例到达 GC Root 的最短调用链深度,在图1右侧 Reference 一栏堆栈中可以直观地看到完整调用链,这时就可以一路追溯找出最可疑的引用,结合代码分析泄漏原因,并对症下药,根治问题。

接下来分析几个我们在项目中遇到一部分典型内存泄漏的案例:

案例剖析

案例1:BitmapBinder 内存泄漏

在涉及跨进程传输 Bitmap 的场景时,我们采用了一种 BitmapBinder 的方法;因为 Intent 支持我们传入自定义的 Binder,因此可以借助 Binder 实现 Intent 传输 Bitmap 对象:

// IBitmapBinder AIDL文件 
import android.graphics.Bitmap; 
interface IBitmapInterface { 
    Bitmap getIntentBitmap(); 
}

然而,Activity1 在使用 BitmapBinderActivity2 传递 Bitmap 后,出现了两个严重的内存泄漏问题:

  1. 跳转后再返回,Activity1 finish 时无法回收;
  2. 反复跳转时,BitmapBinder 对象会反复创建且无法回收;

先分析 Heap Dump:

这是一个『多实例』内存泄漏,即每次 finish Activity1 再打开,都会增加一个 Activity 对象留在 Heap 中,无法销毁;常见于内部类引用、静态数组引用(如监听器列表)等场景;根据 Profiler 提供的引用链,我们找到了 BitmapExt 这个类:

suspend fun Activity.startActivity2WithBitmap() {
    val screenShotBitmap = withContext(Dispatchers.IO) { 
        SDKDeviceHelper.screenShot() 
    } ?: return
    startActivity(Intent().apply {
        val bundle = Bundle()
        bundle.putBinder(KEY_SCREENSHOT_BINDER, object : IBitmapInterface.Stub() {
            override fun getIntentBitmap(): Bitmap {
                return screenShotBitmap
            }
        }) 
        putExtra (INTENT_QUESTION_SCREENSHOT_BITMAP, bundle)
    })
}

BitmapExt 有一个 Activity 的全局扩展方法 startActivity2WithBitmap,里面创建了一个 Binder,将获取到的屏幕截图 Bitmap 丢进去,并包在 Intent 中发送到 Activity2 ;显然这里有个IBitmapInterface的匿名内部类,看来泄漏是从这里发生的;

但有两个疑问,一是这个内部类是写在方法里的,方法结束时,不会把方法栈中的内部类引用清除掉吗?二是这个内部类也并没有引用到 Activity 吧?

要搞明白这两点,就要把 Kotlin 代码反编译成 Java 看看了:

@Nullable
public static final Object startActivity2WithBitmap(@NotNull Activity $this$startActivity2WithBitmap, boolean var1, @NotNull Continuation var2) {
    ...
    Bitmap var14 = (Bitmap)var10000;
    if (var14 == null) {
        return Unit.INSTANCE;
    } else {
        Bitmap screenShotBitmap = var14;
        Intent var4 = new Intent();
        int var6 = false;
        Bundle bundle = new Bundle();
        // 内部类创建位置:
        bundle.putBinder("screenShotBinder", (IBinder)(new BitmapExtKt$startActivity2WithBitmap$$inlined$apply$lambda$1($this$startActivity2WithBitmap, screenShotBitmap)));
        var4.putExtra("question_screenshot_bitmap", bundle);
        Unit var9 = Unit.INSTANCE;
        $this$startActivity2WithBitmap.startActivity(var4);
        return Unit.INSTANCE;
    }
}

// 这是kotlin compiler自动生成的一个普通类:
public final class BitmapExtKt$startActivity2WithBitmap$$inlined$apply$lambda$1 extends IBitmapInterface.Stub {
    // $FF: synthetic field
    final Activity $this_startActivity2WithBitmap$inlined; // 引用了activity
    // $FF: synthetic field
    final Bitmap $screenShotBitmap$inlined;

    BitmapExtKt$startActivity2WithBitmap$$inlined$apply$lambda$1(Activity var1, Bitmap var2) {
        this.$this_startActivity2WithBitmap$inlined = var1;
        this.$screenShotBitmap$inlined = var2;
    }
    @NotNull
    public Bitmap getIntentBitmap() {
        return this.$screenShotBitmap$inlined;
    }
}

在 Kotlin Compiler 编译生成的 Java 文件中,IBitmapInterface 匿名内部类被替换为普通类 BitmapExtKt$startActivity2WithBitmap$$inlined$apply$lambda$1,并且这个普通类持有了 Activity。出现这个情况的原因是,Kotlin 为了在该类的内部能正常使用方法内的变量,把方法的入参以及内部类代码以上创建的所有变量都写进了该类的成员变量中;因此 Activity 被该类引用;另外 Binder 本身生命周期长于 Activity,因此产生内存泄漏。

解决方法是,直接声明一个普通类,即可绕过 Kotlin Compiler 的“优化”,移除 Activity 的引用。

class BitmapBinder(private val bitmap: Bitmap): IBitmapInterface.Stub() {
    override fun getIntentBitmap( ) = bitmap
}

// 使用:
bundle.putBinder(KEY_SCREENSHOT_BINDER, BitmapBinder(screenShotBitmap))

接下来,问题是 Bitmap 和 Binder 会反复创建且无法回收的问题,内存现象如图,每次跳转再关闭,内存都会上涨一点,如同阶梯;GC 后无法释放;

heap 中,通过 Bitmap 尺寸 2560x1600, 320density 可以推断,这些都是未能回收的截图 Bitmap 对象,被 Binder 持有;但查看 Binder 的引用链,却并没有发现任何被我们应用相关的引用;

我们推测 Binder 应该是被生命周期较长的 Native 层引用了,与 Binder 的实现有关,但没找到回收 Binder 的有效方法;

一种解决办法是,复用 Binder,确保每次打开 Activity2 时,Binder 不会重复创建;另外将 BitmapBinder 的 Bitmap 改为弱引用,这样即使 Binder 不能回收,Bitmap 也能被及时回收,毕竟 Bitmap 才是内存大户。

object BitmapBinderHolder {
    private var mBinder: BitmapBinder? = null // 保证全局只有一个BitmapBinder

    fun of(bitmap: Bitmap): BitmapBinder {
        return mBinder ?: BitmapBinder(WeakReference(bitmap)).apply { mBinder = this }
    }
}

class BitmapBinder(var bitmapRef: WeakReference<Bitmap>?): IBitmapInterface.Stub() {
    override fun getIntentBitmap() = bitmapRef?.get()
}

// 使用:
bundle.putBinder(KEY_SCREENSHOT_BINDER, BitmapBinderHolder.of(screenShotBitmap))

验证:如内存图,一次 GC 后,创建的所有 Bitmap 都可以正常回收。

案例2:Flutter 多引擎场景 插件内存泄漏

有不少项目使用了多引擎方案实现 Flutter 混合开发,在 Flutter 页面关闭时,为避免内存泄漏,不但要将 FlutterViewFlutterEngineMessageChannel 等相关组件及时解绑销毁,同时也需要关注各个 Flutter 插件是否有正常的释放操作。

例如在我们的一个多引擎项目中,通过反复打开关闭一个页面,发现了一个内存泄漏点:

这个activity是一个二级页面,使用多引擎方案,在上面跑了一个 FlutterView ;看样子是一个『单实例』的内存泄漏,即无论开关多少次,Activity 只会保留一个实例在heap中无法释放,常见的场景是全局静态变量的引用。这种内存泄漏对内存的影响比多实例泄漏略轻一点,但如果这个 Activity 体量很大,持有较多的 Fragment、View,这些相关组件一起泄漏的话,也是要着重优化的。

从引用链来看,这是 FlutterEngine 内的一个通信 Channel 引起的内存泄漏;当 FlutterEngine 被创建时,引擎内的每个插件会创建出自己的MessageChannel并注册到FlutterEngine.dartExecutor.binaryMessenger中,以便每个插件都能独立和 Native 通信。

例如一个普通插件的写法可能是这样:

class XXPlugin: FlutterPlugin {
    private val mChannel: BasicMessageChannel<Any>? = null

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { // 引擎创建时回调
        mChannel = BasicMessageChannel(flutterPluginBinding.flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME, JSONMessageCodec.INSTANCE)
        mChannel?.setMessageHandler { message, reply ->
            ...
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { // 引擎销毁时回调
        mChannel?.setMessageHandler(null)
        mChannel = null
    }
}

可以看到其实 FlutterPlugin 其实是会持有 binaryMessenger 的引用的,而 binaryMessenger 又会有 FlutterJNI 的引用… 这一系列引用链最终会使 FlutterPlugin 持有 Context,因此如果插件没有正确释放引用,就必然会出现内存泄漏。

我们看下上图引用链中 loggerChannel 的写法是怎么样的:

class LoggerPlugin: FlutterPlugin {
    override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        LoggerChannelImpl.init(flutterPluginBinding.getFlutterEngine())
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    }
}

object LoggerChannelImpl { // 这是一个单例
    private var loggerChannel: BasicMessageChannel<Any>?= null

    fun init(flutterEngine: FlutterEngine) {
        loggerChannel = BasicMessageChannel(flutterEngine.dartExecutor.binaryMessenger, LOGGER_CHANNEL, JSONMessageCodec.INSTANCE)
        loggerChannel?.setMessageHandler { messageJO, reply ->
            ...
        }
    }
}

LoggerPlugin.onAttachedToEngine 中,将 FlutterEngine 传入到了单例 LoggerChannelImpl 里面,binaryMessenger 被单例持有,且 onDetachedFromEngine 方法未做销毁操作,因此一直被单例引用,context无法释放。

这个插件可能在设计时,没有考虑到多引擎的场景;单引擎时,插件的 onAttachedToEngineonDetachedFromEngine 相当于是跟着应用的生命周期走的,因此不会出现内存泄漏;但在多引擎场景下,DartVM 会为每个引擎分配 isolate,和进程有些类似;isolate 的 dart 堆内存是完全独立的,因此引擎之间任何对象(包括静态对象)都不互通;因此 FlutterEngine 会在自己的 isolate 中创建各自的 FlutterPlugin 实例,这使得每次创建引擎,插件的生命周期都会重走一遍。当销毁一个引擎时,插件没有正常回收,没有及时释放 ContextFlutterEngine 的相关引用,就会出现内存泄漏。

修改方案:

  1. LoggerChannelImpl 无需使用单例写法,替换为普通类即可,确保每个引擎的 MessageChannel都是独立的;
  2. LoggerPlugin.onDetachedFromEngine 需要对 MessageChannel 做销毁和置空操作;
案例3:三方库 Native 引用 内存泄漏

项目中接入了一个三方阅读器 SDK,在一次内存分析时,发现每次打开该阅读器,内存便会上升一截并且无法下降;从 heap dump 文件看,Profiler 并未指出项目中存在内存泄漏,但可以看到 app heap 中有一个 Activity 未能回收的实例个数非常多,且内存占用较大。

查看 GCRoot References,发现这些 Activity 没有被任何已知的 GCRoot 引用:

毫无疑问这个 Activity 是存在内存泄漏的,因为操作的时候已经把相关页面都 finish 掉并且手动 GC,因此原因只能是 Activity 被某个不可见的 GCRoot 引用了。

事实上,Profiler 的 Heap Dump 只会显示 Java 堆内存的 GCRoot,而在 Native 堆中的 GCRoot 并不会显示到这个引用列表中。所以,有没有可能是这个Activity被 Native 对象持有了?

我们用动态分析工具 Allocations Record 看一下 Java 类在 Native 堆的引用,果然发现了这个 Activity 的一些引用链:

但可惜引用链都是一些内存地址,没有显示类名,没法知道是何处引用到了 Activity;后面用 LeakCanary 试了一下,虽然也明确说明了是 Native 层 Global Variable 的引用造成的内存泄漏,但还是没有提供具体的调用位置;

我们只好回到源码去分析下可能的调用处了。这个是 DownloadActivity 是我们为了适配阅读器SDK做的一个书籍下载的页面;当本地没有图书时,会先下载书籍文件,随后传入 SDK 中,打开 SDK 自己的 Activity;因此,DownloadActivity 的功能就是下载、校验、解压书籍,并处理 SDK 阅读器的一些启动流程。

按常规思路,先检查下载、校验、解压的代码,都没有发现疑点,listener 之类的都做了弱引用封装;因此推测是 SDK 自身的写法导致的内存泄漏。

发现阅读器 SDK 启动时,有一个 context 入参:

class DownloadActivity {
    ... 
    private fun openBook() {
        ... 
        ReaderApi.getInstance().startReader(this, bookInfo) 
    } 
}

由于这个 SDK 的源码都是混淆过的,只能硬啃了,从 startReader 方法点进去一路跟踪调用链:

class ReaderApi: void startReader(Activity context, BookInfo bookInfo) 
        ↓ 
class AppExecutor: void a(Runnable var1) 
        ↓ 
class ReaderUtils: static void a(Activity var0, BookViewerCallback var1, Bundle var2) 
        ↓ 
class BookViewer: static void a(Context var0, AssetManager var1) 
        ↓ 
class NativeCpp: static native void initJNI(Context var0, AssetManager var1);

最后到了 NativeCpp 这个类的 initJNI 方法,可以看到这个本地方法把我们的 Activity 传进去了,后续处理不得而知,但基于上面的内存分析我们基本可以断定,正是由于这个方法,Activity 的引用被 Native 的长生命周期对象持有,导致 Activity 出现内存泄漏。

至于为什么 Native 需要用到 context 则没法分析了,我们只能将这个问题反馈给 SDK 供应商,让他们做进一步处理。解决办法也不难:

  1. 在销毁阅读器时及时置空 Activity 引用;
  2. startReader 方法不需要指定 Activity 对象,入参声明改为 Context 即可,外部就可以将 Application Context 传进去。

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还含底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

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

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

相关文章

ROS2 中的轻量级、自动化、受控回放

一、说明 这篇文章描述了一种在 ROS2 中实现受控重播器的轻量级方法。用以测试中将现象重新播放一遍&#xff0c;以实现调参或故障定位的目的。所有源代码都可以在这里找到。该帖子也可在此处获得。 二、问题&#xff1a;不同步重播 任何曾经认真开发过 ROS2 的人都会知道这个问…

microsoft excel 公式 计算本金 利息 月供 和总利息

//可使用 Excel 公式指导算出贷款的月还款额。 PMT(rate, nper, pv, [fv], [type])//返回根据定期固定付款和固定利率而定的投资在已知期间内的本金偿付额。 PPMT(rate, per, nper, pv, [fv], [type])//基于固定利率及等额分期付款方式&#xff0c;返回给定期数内对投资的利息…

C++ Primer----1.5类简介 章节练习

头文件 Sales_item.h #ifndef SALESITEM_H #define SALESITEM_H #include <iostream> #include <string>class Sales_item{ public:Sales_item(const std::string &book):isbn(book),units_sold(0),revenue(0.0){}Sales_item(std::istream &is){ is >&…

在macOS上从源代码构建编译Aseprite到组装成App应用(macOS上如何不开着终端窗口)

这里记录一下自己构建 Aseprite 流程。关于如何构建可能已经有很多文章写了&#xff0c;但是都没有提到一点&#xff1a;启动的时候需要点击可执行程序&#xff0c;这样是需要终端窗口一直开着的的&#xff08;如下图&#xff09;&#xff0c;关闭终端就会退出程序&#xff0c;…

MEIS —— 前端部分基本配置

项目基本配置 这篇文章我们随着上一篇文章继续往下叙述&#xff0c;主要是将element和windicss等开发配置进项目中&#xff0c;以及基本的一些页面和组件给他完成。 1. 安装element plus 运行&#xff1a; npm install element-plus --save 这里我们是按需引入(自动)&#x…

JavaSE学习--数据类型和运算符

&#x1f495;"哪里有人喜欢孤独&#xff0c;只不过更不喜欢失望。"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;JJavaSE学习--数据类型和运算符 Java程序是如何运行的&#xff1f; 一.数据类型 整型&#xff1a; 注意事项&#xff1a; 1.整型…

Android 使用Kotlin封装RecyclerView

文章目录 1.概述2.运行效果图3.代码实现3.1 扩展RecyclerView 3.2 扩展Adapter3.3 RecyclerView装饰绘制3.3.1 以图片实现分割线3.3.2 画网格线3.3.3空白的分割线3.3.4 不同方向上的分割线 3.4 使用方法 1.概述 在一个开源项目上看到了一个Android Kotlin版的RecyclerView封装…

Linux 网络编程

套接字&#xff08;Socket&#xff09;&#xff1a; 通过网络实现跨机通信 作用&#xff1a;一种文件描述符传输层的文件描述符 整个编程中&#xff0c;需要着重注意htonl/htons、ntohl/ntohs、inet_addr等 TCP的C/S实现 循环服务器模型 TCP服务器实现过程 1.创建套接字&a…

Web 中间件怎么玩?

本次主要是聊聊关于 web 中间件&#xff0c; 分为如下四个方面 什么是 web 框架中间件 为什么要使用 web 中间件 如何使用及其原理 哪些场景需要使用中间件 开门见山 web 中间件是啥 Web 框架中的中间件主要指的是在 web 请求到具体路由之前或者之后&#xff0c;会经过一个或…

MyBatis 映射文件(Mapper XML):配置与使用

MyBatis 映射文件&#xff08;Mapper XML&#xff09;&#xff1a;配置与使用 MyBatis是一个强大的Java持久化框架&#xff0c;它允许您将SQL查询、插入、更新和删除等操作与Java方法进行映射。这种映射是通过MyBatis的映射文件&#xff0c;通常称为Mapper XML文件来实现的。本…

正点原子lwIP学习笔记——MQTT协议

1. MQTT简介 MQTT是一种基于客户端服务端架构的发布/订阅模式的消息传输协议。他的设计思想是轻巧、开放、简单、规范&#xff0c;易于实现。这些特点使得他对很多场景来说都是很好的选择&#xff0c;尤其是对于受限的环境如机器与机器的通信&#xff08;M2M&#xff09;以及物…

python根据命令行参数动态导入模块或文件

需求 在命令行运行一个 python 文件&#xff0c;同时传入自定义参数&#xff1a; $ python main.py --nodeTable --actioncreate --data"{name: test2, is_sys_obj: False, encoding: UTF8,datconnlimit: -1, variables: []"希望 main.py 接收命令行参数&#xff0…

1.6.C++项目:仿mudou库实现并发服务器之channel模块的设计

项目完整版在&#xff1a; 文章目录 一、channel模块&#xff1a;事件管理Channel类实现二、提供的功能三、实现思想&#xff08;一&#xff09;功能&#xff08;二&#xff09;意义&#xff08;三&#xff09;功能设计 四、代码&#xff08;一&#xff09;框架&#xff08;二…

快速上手 Docker Swarm:构建分布式容器集群、轻松管理节点和服务

什么是Docker Swarm Docker Swarm 是 Docker 的内置编排工具&#xff0c;它允许将多个 Docker 主机组成一个集群&#xff0c;并以统一的方式管理和部署容器化应用程序。Swarm 提供了高可用性、伸缩性和容错能力&#xff0c;使得应用程序能够在集群中弹性地运行和扩展。 Docke…

唤醒手腕 Matlab 游戏编程常用技术知识点详细教程(更新中)

Figure 窗口初始化 figure 使用默认属性值创建一个新的图窗窗口。生成的图窗为当前图窗。f figure(___) 返回 Figure 对象。可使用 f 在创建图窗后查询或修改其属性。figure(f) 将 f 指定的图窗作为当前图窗&#xff0c;并将其显示在其他所有图窗的上面。 figure(n) 查找 Nu…

8、Docker-compose容器编排

一、Docker compose 是什么 Compose 是 Docker 公司推出的一个工具软件&#xff0c;可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml&#xff0c;写好多个容器之间的调用关系。然后&#xff0c;只要一个命令&#xff0c;就能同…

cadence SPB17.4 S032 - 使用room来放置元件

文章目录 cadence SPB17.4 S032 - 使用room来放置元件概述笔记在orcad中设置子原理图的ROOM号码在空的Allegro工程中, 放入板框在allegro中建立room备注补充 - ROOM还得留着END cadence SPB17.4 S032 - 使用room来放置元件 概述 如果在allegro中直接手工或自动放置元件, 放好…

scala基础入门

一、Scala安装 下载网址&#xff1a;Install | The Scala Programming Language ideal安装 &#xff08;1&#xff09;下载安装Scala plugins &#xff08;2&#xff09;统一JDK环境&#xff0c;统一为8 &#xff08;3&#xff09;加载Scala &#xff08;4&#xff09;创建工…

单调队列 - 滑动窗口

154. 滑动窗口 - AcWing题库 O(1)求窗口中的最大值/最小值 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing namespace std;typedef pair<int, int> PII; typedef long long ll; typedef long double l…

gdb的使用

目录 gdb工具的使用 代码调试相关指令 运行程序指令 r 显示代码的指令 l 给代码打断点 b 查看断点位置 info b 执行代码到断点处停止 关闭断点 d断点编号 关闭某个断点&#xff0c;但不删除 disable编号 打开某个断点 enable断点编号 逐过程调试代码 n 逐语句调试代码 s 查看…