Qt_一个由单例引发的崩溃
Qt_一个由单例引发的崩溃
文章目录
- Qt_一个由单例引发的崩溃
- 摘要
- 关于 Q_GLOBAL_STATIC
- 代码测试
- 布局管理器源码分析
- Demo 验证
- 关于布局管理器析构
- Qt 类声明周期探索
- 更新代码获取父类
- 分析Qt 单例宏源码
关键字:
Qt、
Q_GLOBAL_STATIC、
单例、
UI、
崩溃
摘要
今天简直是令人心力交瘁的一天,在公司被一个顽固的Bug纠缠了整整一天。一开始,我对这个问题的认知并不深刻,只是觉得有点小瑕疵,于是比较轻松地着手解决。我开始摸索着定位问题,态度上也没太在意,毕竟在我看来,这只是一场小小的技术挑战。
然而,随着时间的推移,我逐渐意识到问题的严重性。逐渐加深的烦躁和困扰让我开始感到不安。在一度对问题轻描淡写的态度下,我终于被迫正视这个Bug所可能引发的连锁反应。随着这个问题的逐渐显露出其庞大的影响,我仿佛看到了一个漩涡,正在悄然蔓延着,威胁着整个系统的稳定性。
随着深入的调查和排查,我开始逐渐理解这个Bug的神秘本质,而这个认识过程伴随着我的焦虑逐渐升温。我发现这并非是一个简单的技术故障,而是一个可能牵动公司正常运营的重大问题。这时,我终于感受到了来自责任的重压,因为解决这个问题不仅关乎我的个人技术能力,更涉及到公司业务的持续稳健。
最终,当我对问题的了解达到顶峰时,我不禁意识到这一天的崩溃并非仅限于我的个人情绪,更是对整个系统运行稳定性的一次极大考验。这场对抗Bug的战斗让我感受到了技术领域的不可预测性和挑战性,也让我更加深刻地明白在这个领域中的学无止境。

关于 Q_GLOBAL_STATIC
我在创建单例上偷了懒,使用了Qt Q_GLOBAL_STATIC宏来创建单例。关于Q_GLOBAL_STATIC的解释如下:
Q_GLOBAL_STATIC 是 Qt 框架中用于创建全局静态变量的宏。这个宏的目的是确保在多线程环境下安全地初始化和访问静态变量。在 C++ 中,全局静态变量的初始化顺序可能会带来一些问题,特别是在多线程环境下。
使用 Q_GLOBAL_STATIC 宏,Qt 提供了一种线程安全的机制来创建全局静态变量。这个宏可以确保静态变量只在第一次访问时被初始化,而且在初始化过程中是线程安全的。
具体用法如下:
Q_GLOBAL_STATIC(Type, variable)
其中,Type 是要创建的静态变量的类型,variable 是变量的名称。
举例说明:
#include <QGlobalStatic>class MyClass {
public:MyClass() {// 构造函数}// 其他成员函数和数据成员
};Q_GLOBAL_STATIC(MyClass, myGlobalInstance)
在上面的例子中,myGlobalInstance 就是一个全局静态变量,它的类型是 MyClass。使用 Q_GLOBAL_STATIC 宏,Qt 会在需要时确保 myGlobalInstance 被安全地初始化,并在整个程序运行期间保持其存在。
这种机制的好处是,在多线程环境下,多个线程可以同时访问 myGlobalInstance,而不必担心因为初始化顺序而导致的问题。Qt 在内部使用了一些技巧,比如使用双重检查锁定(double-checked locking)等,以确保在多线程环境下的性能和正确性。
代码测试
所以,按照上面的解释,我使用Q_GLOBAL_STATIC来创建一个单例,应该是没有问题的。但是。
下面看下的Demo 代码
头文件:
#ifndef FORM_H
#define FORM_H#include <QWidget>namespace Ui {
class Form;
}class Form : public QWidget
{Q_OBJECTpublic:explicit Form(QWidget *parent = nullptr);static Form* getInstance();~Form();private:Ui::Form *ui;
};#endif // FORM_H
源文件:
#include "form.h"
#include "ui_form.h"
Q_GLOBAL_STATIC(Form,m_Form)
Form::Form(QWidget *parent) :QWidget(parent),ui(new Ui::Form)
{ui->setupUi(this);
}Form *Form::getInstance()
{return m_Form;
}Form::~Form()
{delete ui;
}
调用文件:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);ui->verticalLayout->addWidget(Form::getInstance());}MainWindow::~MainWindow()
{delete ui;
}
布局管理器源码分析
看到这里不知道你发现问题所在了没有呢。其实到这里,程序运行什么都都是没有问题的,就是不要退出程序,因为一旦退出程序,就会遇到崩溃问题。那么开始我一度以为是使用了布局管理器导致的这个问题,但是我看了源码也却突然发现,布局管理器是不会对其管理的控件进行资源释放的,部分源码如下,我这里就合并显示了。
QBoxLayoutPrivate::~QBoxLayoutPrivate()
{
}/*!Destroys this box layout.The layout's widgets aren't destroyed.
*/
QHBoxLayout::~QHBoxLayout()
{
}/*!The layout's widgets aren't destroyed.
*/
QVBoxLayout::~QVBoxLayout()
{
}
Demo 验证
保险起见,我还特意谷歌翻译了一下Destroys this box layout.The layout's widgets aren't destroyed.里面明确说明,布局会破坏,但是里面的控件不会释放。所谓问题来,到底是哪里的问题了,我尝试了另一种写法,代码如下,希望你还可以跟得上
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);Form * temp = Form::getInstance();temp->setParent(this);temp->show();
}MainWindow::~MainWindow()
{delete ui;
}
这样我得到的结果同样是崩溃的析构函数,如下图所示

关于布局管理器析构
正当我一头雾水的手,我随机搜了一下Qt 布局管理器在析构是会析构子控件吗这个问题,得到的回答如下
在 Qt 中,布局管理器在析构时通常不会主动析构其管理的子控件。布局管理器负责管理控件的位置和大小,而控件的生命周期管理通常是由其父控件负责的。
当一个 QWidget 对象(可能是窗口或其他容器控件)被析构时,它会负责删除其子控件。这是因为 QWidget 继承自 QObject,而 QObject 有一个父子对象关系系统,负责自动管理子对象的内存。
布局管理器在 Qt 中主要用于确定子控件的布局,而不是控制它们的生命周期。因此,当布局管理器本身被析构时,它不会主动销毁其管理的子控件。子控件的析构通常是由其父控件来处理。
如果你希望在删除布局管理器的同时也删除其管理的子控件,你需要手动迭代子控件并将其删除,或者使用智能指针等工具来管理控件的生命周期。这可以确保在删除布局管理器时,相关的子控件也会被正确地释放。
总的来说,Qt 的布局管理器并不负责子控件的析构,而是留给控件的父对象来管理。
结合上面对布局管理器源码的分析,可以看到,我们的崩溃不是有布局管理器引起的,而是被父类析构析构了。
Qt 类声明周期探索
本着刨根问题的原则,我有问了另一个问题设置了布局管理器就等于设置了父类吗
在 Qt 中,设置了布局管理器并不等同于设置了父类。虽然布局管理器通常是作为容器控件的子对象来管理的,但它们在概念上是不同的。
-
父子关系:
- 当你将一个控件(例如 QWidget)设置为另一个控件的子控件时,它们建立了父子关系。这样做的话,父控件会负责管理子控件的生命周期,包括在父控件销毁时自动删除子控件。
- 布局管理器是一种机制,用于控制子控件的布局,但它们通常不会成为父控件。布局管理器可以安排子控件的位置和大小,但并不会负责管理它们的生命周期。
-
布局管理器的作用:
- 布局管理器是用来管理布局的工具。它们通常被设置在容器类控件上,比如 QWidget、QMainWindow、QDialog 等。布局管理器负责决定子控件的排列方式,但不负责管理它们的析构。
下面是一个简单的例子,演示了如何在 QWidget 中使用布局管理器:
#include <QHBoxLayout>
#include <QPushButton>
#include <QWidget>int main(int argc, char *argv[]) {QApplication app(argc, argv);QWidget *mainWidget = new QWidget;QHBoxLayout *layout = new QHBoxLayout(mainWidget);QPushButton *button1 = new QPushButton("Button 1");QPushButton *button2 = new QPushButton("Button 2");layout->addWidget(button1);layout->addWidget(button2);mainWidget->setLayout(layout);mainWidget->show();return app.exec();
}
在这个例子中,mainWidget 是一个 QWidget,而 layout 是一个 QHBoxLayout。布局管理器 layout 被设置为 mainWidget 的布局管理器。然而,mainWidget 仍然是这两个按钮的父控件,而不是布局管理器。
总的来说,在设置布局管理器时,确保理解父子关系的变化,并注意生命周期管理的责任。
更新代码获取父类
虽然回答的不是那么一回事,但是还是启发了,所以我对我的代码做了一点更新,如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);Form * temp = Form::getInstance();ui->verticalLayout->addWidget(temp);qDebug()<< "temp->parent()->" << temp->parent() << endl<< "temp->parent()->parent()->" << temp->parent()->parent() << endl<< "this->"<< this << endl<< "this->parent()->" <<this->parent() << endl<< "ui->verticalLayout->parent()" << ui->verticalLayout->parent();
}MainWindow::~MainWindow()
{delete ui;
}
得到打印信息如下
temp->parent()-> QWidget(0x948920, name = "centralwidget")
temp->parent()->parent()-> MainWindow(0x8ffd20, name = "MainWindow")
this-> MainWindow(0x8ffd20, name="MainWindow")
this->parent()-> QObject(0x0)
ui->verticalLayout->parent() QWidget(0x948920, name = "centralwidget")
这下基本明白了,其实对象的声明周期在Qt里面还是受气父控件的控制,而并非是布局管理器,今天我之所碰巧解决了这个Bug,仅仅是因为当我们吧控件添加到布局管理器时。不管理器把他的费空间做了传递、现在终于明白了。
分析Qt 单例宏源码
那么还需要解决另一个问题,就是我的单例宏做了什么。
#define Q_GLOBAL_STATIC(TYPE, NAME) \Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())
#define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \namespace { namespace Q_QGS_ ## NAME { \typedef TYPE Type; \QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \Q_GLOBAL_STATIC_INTERNAL(ARGS) \} } \static QGlobalStatic<TYPE, \Q_QGS_ ## NAME::innerFunction, \Q_QGS_ ## NAME::guard> NAME;
#if defined(Q_COMPILER_CONSTEXPR)
# define Q_BASIC_ATOMIC_INITIALIZER(a) { a }
#else
# define Q_BASIC_ATOMIC_INITIALIZER(a) { ATOMIC_VAR_INIT(a) }
#endif
#define Q_GLOBAL_STATIC_INTERNAL(ARGS) \Q_GLOBAL_STATIC_INTERNAL_DECORATION Type *innerFunction() \{ \struct HolderBase { \~HolderBase() noexcept \{ if (guard.loadRelaxed() == QtGlobalStatic::Initialized) \guard.storeRelaxed(QtGlobalStatic::Destroyed); } \}; \static struct Holder : public HolderBase { \Type value; \Holder() \noexcept(noexcept(Type ARGS)) \: value ARGS \{ guard.storeRelaxed(QtGlobalStatic::Initialized); } \} holder; \return &holder.value; \}
#else
// We don't know if this compiler supports thread-safe global statics
// so use our own locked implementationQT_END_NAMESPACE
#include <QtCore/qmutex.h>
#include <mutex>
QT_BEGIN_NAMESPACE#define Q_GLOBAL_STATIC_INTERNAL(ARGS) \Q_DECL_HIDDEN inline Type *innerFunction() \{ \static Type *d; \static QBasicMutex mutex; \int x = guard.loadAcquire(); \if (Q_UNLIKELY(x >= QtGlobalStatic::Uninitialized)) { \const std::lock_guard<QBasicMutex> locker(mutex); \if (guard.loadRelaxed() == QtGlobalStatic::Uninitialized) { \d = new Type ARGS; \static struct Cleanup { \~Cleanup() { \delete d; \guard.storeRelaxed(QtGlobalStatic::Destroyed); \} \} cleanup; \guard.storeRelease(QtGlobalStatic::Initialized); \} \} \return d; \}
#endif
让我们稍作翻译
这段宏定义了一个模板化的全局静态变量创建机制,通过 Q_GLOBAL_STATIC_INTERNAL 宏可以方便地创建一个全局静态变量。这个机制使用了 C++11 的特性,包括 noexcept 说明符和内存模型的一些操作。
让我们逐步解释这个宏的各个部分:
-
Q_GLOBAL_STATIC_INTERNAL(ARGS): 这是主要的宏定义,用于创建全局静态变量。ARGS是传递给 Type 类型构造函数的参数。 -
Q_GLOBAL_STATIC_INTERNAL_DECORATION: 这是一个在宏中使用的修饰符,可能是空的,取决于编译器对 C++11 特性的支持情况。 -
Type *innerFunction(): 这是一个内部的静态函数,负责实际创建和返回全局静态变量的指针。它使用了一个内部的 Holder 类,以确保在全局静态变量的析构期间正确地管理生命周期。 -
struct HolderBase: 这是一个基础结构,其中的析构函数负责在全局静态变量被销毁时,检查并更新一个状态标志。这个标志在构造时被设置为 Initialized,而在析构时被设置为 Destroyed。 -
struct Holder : public HolderBase: 这是一个派生自 HolderBase 的结构。它包含了具体的 Type 类型的实例(value),并在构造函数中将状态标志设置为 Initialized。这确保在全局静态变量的生命周期内,HolderBase 的析构函数将被正确调用,以更新状态标志。
整个机制的目的是确保在多线程环境下,全局静态变量的创建是线程安全的。通过在 HolderBase 的析构函数中检查状态标志,可以防止在全局静态变量析构时重复析构。
这是一个比较复杂的实现,主要是为了保证全局静态变量的正确创建和销毁,并且在多线程环境下能够安全使用。
所以现在应该明白了,这就是资源双重释放了,未来的你,加油。

相关文章:
Qt_一个由单例引发的崩溃
Qt_一个由单例引发的崩溃 文章目录 Qt_一个由单例引发的崩溃摘要关于 Q_GLOBAL_STATIC代码测试布局管理器源码分析Demo 验证关于布局管理器析构Qt 类声明周期探索更新代码获取父类分析Qt 单例宏源码 关键字: Qt、 Q_GLOBAL_STATIC、 单例、 UI、 崩溃 摘要 今…...
P8A004-系统加固-磁盘访问权限
【预备知识】 访问权限,根据在各种预定义的组中用户的身份标识及其成员身份来限制访问某些信息项或某些控制的机制。访问控制通常由系统管理员用来控制用户访问网络资源(如服务器、目录和文件)的访问,并且通常通过向用户和组授予…...
数智赋能 锦江汽车携手苏州金龙打造高质量盛会服务
作为一家老牌客运公司,成立于1956年的上海锦江汽车服务有限公司(以下简称锦江汽车),拥有1200多辆大巴和5000多辆轿车,是上海乃至长三角地区规模最大的专业旅游客运公司。面对客运市场的持续萎缩,锦江汽车坚…...
kolla-ansible 部署OpenStack云计算平台
目录 一、环境 二、安装及部署 三、测试 一、环境 官方文档:https://docs.openstack.org/kolla-ansible/yoga/user/quickstart.html rhel8.6 网络设置: 修改网卡名称 网络IP: 主机名: 网络时间协议 配置软件仓库 vim docke…...
wireshark 抓包提示
[TCP Previous segment not captured] 在TCP的传输阶段,同一台主机发出的数据段应该是连续的,即后一个包的Seq等于前一个包的SeqLen(三次握手和四次挥手是个例外)。如果wireshark发现后一个包的Seq号大于前一个包的SeqLen…...
Redis未授权访问-CNVD-2019-21763复现
Redis未授权访问-CNVD-2019-21763复现 利用项目: https://github.com/vulhub/redis-rogue-getshell 解压后先进入到 RedisModulesSDK目录里面的exp目录下,make编译一下才会产生exp.so文件,后面再利用这个exp.so文件进行远程代码执行 需要p…...
汇编:常用的输入与输出
1.字符输出 使用int 21h中断的02h号功能可以在屏幕输出一个字符,dl中存放要输出字符的ascii码。 如下代码将在屏幕输出一个字符“a”: mov ah,02hmov dl,aint 21h 2.字符输入 使用int 21h中断的01h号功能可以接受一个字符,al存放输…...
MYSQL基础之【正则表达式,事务处理】
文章目录 前言MySQL 正则表达式MySQL 事务事务控制语句事务处理方法PHP中使用事务实例 后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:Mysql 🐱👓博主在前端领域还有很多知识和技术需要掌握,正在不…...
Mysql并发时常见的死锁及解决方法
使用数据库时,有时会出现死锁。对于实际应用来说,就是出现系统卡顿。 死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。就是所谓的锁资源请求产生了回路现象,即死循环,此时称系统处于…...
二十九、微服务案例完善(数据聚合、自动补全、数据同步)
目录 一、定义 二、分类 1、桶(Bucket)聚合: 2、度量(Metric)聚合: 3、管道聚合(Pipeline Aggregation): 4、注意: 参与聚合的字段类型必须是: 三、使用DSL实现聚合 聚合所必须的三要素: 聚合可配…...
vue 目录树的展开与关闭
目录 1、翻页方法中控制目录树节点的展开与关闭2、搜索目录树节点名称控制节点的展开与关闭 <el-tree:data"data_option"ref"tree":props"defaultProps"node-click"handleNodeClick":default-expanded-keys"needExpandedKeys&…...
【Docker】python flask 项目如何打包成 Docker images镜像 上传至阿里云ACR私有(共有)镜像仓库 集成Drone CI
一、Python环境编译 1、处理好venv环境 要生成正常的 requirements.txt 文件,我们就需要先将虚拟环境处理好 创建虚拟环境(可选): 在项目目录中,你可以选择使用虚拟环境,这样你的项目依赖将被隔离在一个…...
力扣labuladong——一刷day55
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣951. 翻转等价二叉树二、力扣124. 二叉树中的最大路径和三、力扣112. 路径总和(遍历)四、力扣112. 路径总和(分解&a…...
springboot实现验证码功能
转载自 : www.javaman.cn 1、编写工具类生成4位随机数 该工具类主要生成从0-9,a-z,A-Z范围内产生的4位随机数 /*** 产生4位随机字符串*/public static String getCheckCode() {String base "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn…...
内测分发平台是否支持应用的微服务化部署
内测分发平台的微服务化部署支持是现代应用开发和部署的一个重要特性。首先我们得知道什么是微服务化部署都有哪些关键功能,如何实施微服务化的部署。下文以我自己理解总结了几点。 图片来源:news.gulufenfa.com 微服务是一种基于独立运行的小型服务来构建应用程序…...
1140. 最短网络,prim算法,模板题
1140. 最短网络 - AcWing题库 农夫约翰被选为他们镇的镇长! 他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。 约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。 约翰的农场的编号是1…...
升级jdk17过程中,原来的jdk8下的webservice客户端怎样处理
背景:之前jdk8环境下,使用的cxf框架,而且是动态加载解析作为客户端。大家一直相处的很愉快。但是最近升级jdk17,发现cxf不好用了。网上百度,大部分都是说升级cxf版本,并且添加jaxb的相关依赖就可以了。但是…...
Verilog基本语法概述
一、概述 Verilog 是一种用于数字逻辑电路设计的硬件描述语言,可以用来进行数字电路的仿真验证、时序分析、逻辑综合。 既是一种 行为级(可用于电路的功能描述) 描述语言又是一种 结构性(可用于元器件及其之间的连接)…...
论文阅读:C2VIR-SLAM: Centralized Collaborative Visual-Inertial-Range SLAM
前言 论文全程为C2VIR-SLAM: Centralized Collaborative Visual-Inertial-Range Simultaneous Localization and Mapping,是发表在MDPI drones(二区,IF4.8)上的一篇论文。这篇文章使用单目相机、惯性测量单元( IMU )和UWB设备作为…...
蓝桥杯刷题day01——字符串中的单词反转
题目描述 你在与一位习惯从右往左阅读的朋友发消息,他发出的文字顺序都与正常相反但单词内容正确,为了和他顺利交流你决定写一个转换程序,把他所发的消息 message 转换为正常语序。 注意:输入字符串 message 中可能会存在前导空…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
Windows 下端口占用排查与释放全攻略
Windows 下端口占用排查与释放全攻略 在开发和运维过程中,经常会遇到端口被占用的问题(如 8080、3306 等常用端口)。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口,帮助你高效解决此类问题。 一、准…...
