Qt Signals Slots VS QEvents - Qt跨线程异步操作性能测试与选取建议
相关代码参考:https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/qt_event_signal
1.问题的由来
在对 taskBus 进行低延迟改造时,避免滥用信号与槽起到了较好的作用。笔者在前一篇文章中,叙述了通过避免广播式地播发信号,以及频繁的 new 与 delete 来提高软件无线电(SDR)平台的吞吐。近期,考虑到跨线程异步操作其实事件(QEvent)可能更加适合点对点的调用,遂把taskBus的主数据流转使用 Events 进行了改造,收到了大概5-10ms的提升。
这个提升还是没有达到我的期望,因为印象里,信号与槽是非常慢的。那么,问题来了,跨线程 Signal&Slots 与 Events 到底谁要快,为什么快,以及快多少呢?
2. 回顾“信号槽很慢”的印象的源头
在经典的Qt文档里,有一段对信号与槽性能选择的性能描述:
Compared to callbacks, signals and slots are slightly slower because of the
increased flexibility they provide, although the difference for real applications
is insignificant. In general, emitting a signal that is connected to some slots,is approximately ten times slower than calling the receivers directly, with non-virtual function calls.
“与回调相比,信号和插槽的速度稍慢,因为它们提供了更大的灵活性,尽管实际应用程序的差异并不显著。通常,发射连接到某些插槽的信号比直接调用接收器(使用非虚拟函数调用)慢大约十倍。”
这就是印象的源头了。不过,带着上述问题,仔细阅读,发现还有更多的解释:
This is the overhead required to locate the connection object, to safely iterate over all connections (i.e. checking that subsequent receivers have not been destroyed during the emission), and to marshall any parameters in a generic fashion. While ten non-virtual function calls may sound like a lot, it's much less overhead than any new or delete operation, for example.As soon as you perform a string, vector or list operation that behind the scene requires new or delete, the signals and slots overhead is only responsible for a very small proportion of the complete function call costs. The same is true whenever you do a system call in a slot; or indirectly call more than ten functions. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won't even notice.
“这是定位连接对象、安全地迭代所有连接(即检查后续接收器在发射过程中是否未被破坏)以及以通用方式整理任何参数所需的开销。虽然十个非虚拟函数调用听起来可能很多,但它的开销比任何new操作或delete操作都要小得多。一旦执行了一个字符串、向量或列表操作,而该操作在后台需要新建或删除,则信号和插槽开销只占整个函数调用成本的一小部分。每当您在插槽中进行系统调用时,情况也是如此;或者间接调用十多个函数。信号和插槽机制的简单性和灵活性非常值得开销,而您的用户甚至不会注意到这一点。”
会不会,上次的优化起到关键作用的不是信号与槽改成了直接函数调用,而是把频繁new delete换成静态内存导致的提升?我立刻进行了测试,发现的确如此。
- new 和 delete的开销远远大于 signal&slots的开销
3. 编程进行专门测试
我们使用Qt专门对信号与槽、事件这两种跨线程传递消息的方法来进行测试。设计测试由两个对象之间互相以最快速度乒乓消息为场景,如下图所示:
含有时戳的消息,可以用来计算和统计平均延迟(消息生成和被处理的时差),以及最终的处理能力。
测试分为信号-槽测试,事件测试,以及单线程对比调用测试。测试进行10000次调用,并模拟实际程序对传递的消息进行一些处理,如产生一定长度的字符串。
通过观察不同长度下的开销,即可直挂感受消息传递开销与处理开销的占比。
3.1 用于测试的消息体
消息体既用于信号与槽测试,也用于Event测试以及直接调用。这样参数中都直接new消息,对大家是公平的。
testevent.h---------------#include <QEvent>
#include <time.h>
#include <QString>
class TestMsg : public QEvent
{
private:static QEvent::Type m_testEvt;static QEvent::Type m_startEvt;static QEvent::Type m_startSig;static QEvent::Type m_quit;clock_t m_clk = 0;QString m_dummyLongStr;
public:TestMsg(clock_t clk);~TestMsg();inline clock_t clock() {return m_clk;}inline void fillStr(int len){for (int i=0;i<len;++i){m_dummyLongStr.push_back((char)(i*37%64+32));}}
public:static inline QEvent::Type type() {return m_testEvt;}static inline QEvent::Type startEvt() {return m_startEvt;}static inline QEvent::Type startSig() {return m_startSig;}static inline QEvent::Type quitEvt() {return m_quit;}
};//testevent.cpp---------------
#include "testevent.h"
#include <QDebug>
//Regisit a event
QEvent::Type TestMsg::m_testEvt = (QEvent::Type)QEvent::registerEventType();
QEvent::Type TestMsg::m_startEvt = (QEvent::Type)QEvent::registerEventType();
QEvent::Type TestMsg::m_startSig = (QEvent::Type)QEvent::registerEventType();
QEvent::Type TestMsg::m_quit = (QEvent::Type)QEvent::registerEventType();TestMsg::TestMsg(clock_t clk):QEvent(m_testEvt),m_clk(clk)
{}
TestMsg::~TestMsg()
{}
消息体含有一个clock时戳,用于计算传递耗时。同时,有一个fillStr的耗时操作,用于模拟真实的有效数据处理。
3.2 测试对象
测试对象直接派生自 QObject,将在线程中执行测试。为了模拟信号与槽的 meta 开销,设置了26组信号与槽,进行交叉连接,并用第16组进行测试。
#ifndef TESTOBJ_H
#define TESTOBJ_H#include <QObject>
#include <QEvent>
#include "testevent.h"
class TestObj : public QObject
{Q_OBJECT
public:explicit TestObj(QObject *parent = nullptr);void setBuddy(TestObj * buddy) {m_buddy = buddy;buddy->m_buddy = this;}void runDirectCall();
public slots:void test_slot1(QEvent * evt){}//...void test_slot16(QEvent * evt);//...void test_slot26(QEvent * evt){}
signals:void test_sig1(QEvent * evt);//...void test_sig26(QEvent * evt);void evt_finished();void sig_finished();
protected:void customEvent(QEvent *) override;
private:clock_t m_nFirstClkSig = -1;quint32 m_nCount_Sigs = 0;clock_t m_nFirstClkEvt = -1;quint32 m_nCount_Evts = 0;clock_t m_nFirstClkDir = -1;quint32 m_nCount_Dir = 0;
private:TestObj * m_buddy = nullptr;
private:void run_signal();void run_event();
private:void direct_call(QEvent * evt);
};#endif // TESTOBJ_H
无论以何种接口获得 TestMsg,都执行相同的操作:
//以信号槽为例
void TestObj::test_slot16(QEvent * evt)
{clock_t curr_clk = clock();if (m_nFirstClkSig==-1)m_nFirstClkSig = curr_clk;TestMsg * e = dynamic_cast<TestMsg *>(evt);if (e){++m_nCount_Sigs;e->fillStr(fillStrLen);if (m_nCount_Sigs==testCounts){QTextStream strm(stdout);strm << objectName()<< QString().asprintf(" (%llX) run %d Signals, total costs %.2lf ms, AVG cost %.2lf us / test.\n",(unsigned long long)this,(int)(m_nCount_Sigs),1e3 * (curr_clk - m_nFirstClkSig) / CLOCKS_PER_SEC,1e6 * (curr_clk - m_nFirstClkSig) / CLOCKS_PER_SEC / testCounts);strm.flush();emit sig_finished();}delete e;}
}void TestObj::run_signal()
{for (int i=0;i<testCounts;++i)emit test_sig16(new TestMsg(clock()));
}
3.3 测试方法
在 main函数中进行测试:
#include <QCoreApplication>
#include <QThread>
#include "testevent.h"
#include "testobj.h"
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);clock_t clk_start = clock();printf("StartClk = %d\n",clk_start);QThread::msleep(100);QThread * thread1 = new QThread;QThread * thread2 = new QThread;TestObj * obj1 = new TestObj;TestObj * obj2 = new TestObj;obj1->setObjectName("OBJ1");obj2->setObjectName("OBJ2");obj1->setBuddy(obj2);obj1->connect(obj1,&TestObj::test_sig1,obj2,&TestObj::test_slot16,Qt::QueuedConnection);//...obj1->connect(obj1,&TestObj::test_sig26,obj2,&TestObj::test_slot16,Qt::QueuedConnection);obj1->connect(obj2,&TestObj::test_sig16,obj1,&TestObj::test_slot1,Qt::QueuedConnection);//...obj1->connect(obj2,&TestObj::test_sig16,obj1,&TestObj::test_slot26,Qt::QueuedConnection);obj1->moveToThread(thread1);obj2->moveToThread(thread2);thread1->start();thread2->start();printf("Test signals & slots, Events...\n");QCoreApplication::processEvents();a.connect (obj1,&TestObj::sig_finished,[=]()->void{QThread::msleep(1000);QCoreApplication::postEvent(obj1,new QEvent(TestMsg::startEvt()));});a.connect (obj2,&TestObj::sig_finished,[=]()->void{QThread::msleep(1000);QCoreApplication::postEvent(obj2,new QEvent(TestMsg::startEvt()));});a.connect (obj1,&TestObj::evt_finished,[=]()->void{QThread::msleep(1000);QCoreApplication::postEvent(obj1,new QEvent(TestMsg::quitEvt()));});a.connect (obj2,&TestObj::evt_finished,[=]()->void{QThread::msleep(1000);QCoreApplication::postEvent(obj2,new QEvent(TestMsg::quitEvt()));});QThread::msleep(1000);QCoreApplication::processEvents();QCoreApplication::postEvent(obj1,new QEvent(TestMsg::startSig()));QCoreApplication::postEvent(obj2,new QEvent(TestMsg::startSig()));a.processEvents();thread1->wait();thread2->wait();QThread::msleep(2000);obj1->runDirectCall();printf("Finished.\n");thread1->deleteLater();thread2->deleteLater();obj1->deleteLater();obj2->deleteLater();QThread::msleep(1000);QCoreApplication::processEvents();return 0;
}
4. 测试结果
测试环境:i7 10代 win11 Mingw64 Qt6.6
4.1 Debug, Strlen=0,20000次
StartClk = 2
Test signals & slots, Events...
OBJ1 (1AD52AD73E0) run 20000 Signals, total costs 2089.00 ms, AVG cost 104.45 us / test.
OBJ2 (1AD52AD74C0) run 20000 Signals, total costs 32.00 ms, AVG cost 1.60 us / test.
OBJ2 (1AD52AD74C0) run 20000 Events, total costs 14.00 ms, AVG cost 0.70 us /test.
OBJ1 (1AD52AD73E0) run 20000 Events, total costs 27.00 ms, AVG cost 1.35 us /test.
Test Direct Call...
OBJ1 (1AD52AD73E0) run 20000 Direct Calls, total costs 6.00 ms, AVG cost 0.30 us / test.
Finished.
4.2 Debug, Strlen=1000,20000次
StartClk = 2
Test signals & slots, Events...
OBJ1 (21AFB83BBA0) run 20000 Signals, total costs 2541.00 ms, AVG cost 127.05 us / test.
OBJ2 (21AFB83B820) run 20000 Signals, total costs 1630.00 ms, AVG cost 81.50 us / test.
OBJ2 (21AFB83B820) run 20000 Events, total costs 1373.00 ms, AVG cost 68.65 us /test.
OBJ1 (21AFB83BBA0) run 20000 Events, total costs 1403.00 ms, AVG cost 70.15 us /test.
Test Direct Call...
OBJ1 (21AFB83BBA0) run 20000 Direct Calls, total costs 1377.00 ms, AVG cost 68.85 us / test.
Finished.
4.3 Release, Strlen=0,100000次
StartClk = 5
Test signals & slots, Events...
OBJ1 (294D24BB5F0) run 100000 Signals, total costs 2236.00 ms, AVG cost 22.36 us / test.
OBJ2 (294D24BB930) run 100000 Signals, total costs 25.00 ms, AVG cost 0.25 us / test.
OBJ2 (294D24BB930) run 100000 Events, total costs 12.00 ms, AVG cost 0.12 us /test.
OBJ1 (294D24BB5F0) run 100000 Events, total costs 38.00 ms, AVG cost 0.38 us /test.
Test Direct Call...
OBJ1 (294D24BB5F0) run 100000 Direct Calls, total costs 14.00 ms, AVG cost 0.14 us / test.
Finished.
4.4 Release, Strlen=1000,100000次
StartClk = 4
Test signals & slots, Events...
OBJ1 (154799851A0) run 100000 Signals, total costs 1358.00 ms, AVG cost 13.58 us / test.
OBJ2 (154799854E0) run 100000 Signals, total costs 620.00 ms, AVG cost 6.20 us / test.
OBJ2 (154799854E0) run 100000 Events, total costs 543.00 ms, AVG cost 5.43 us /test.
OBJ1 (154799851A0) run 100000 Events, total costs 547.00 ms, AVG cost 5.47 us /test.
Test Direct Call...
OBJ1 (154799851A0) run 100000 Direct Calls, total costs 527.00 ms, AVG cost 5.27 us / test.
Finished.
5. 结果分析
可以看见,
- Release下,Event 和 直接调用的性能几乎完全一样。Debug下略慢。
- Release下,如果发射的信号只对应一个槽,则比直接调用的性能稍微差一些,但远没有10倍的差距,比如Obj2发射的某个信号,只连接到Obj1 的1个槽,其实区别不大。
- Release下,如果发射的信号对应多个槽,则性能显著下降,正如obj1发射的一个信号,连接到多个槽,结果就很耗时。发射的信号会给每个槽都走一遭。
- 如果存在大量的new\delete,则无论何方式,区别都不大。new/delete非常耗时。
6. 开发建议
对于密集的点对点异步调用,显然是Event比较好。如果是广播性质的多对多,Event需要循环,则使用信号与槽会利于开发。此外信号与槽会自动维护双方的可用性,在目的析构后不再调用槽。Events因为要给入指针,则必须自己确保指针的有效性。
提高速度的关键还是使用静态内存,避免频繁new、delete。
7. 改进效果
taskBus的异步调用最后一步队列操作,使用Event而不是信号,来通知线程干活,只获得了大概10ms的延迟优化,是因为大部分延迟实际是在缓存、滤波器上,并不在信号-槽中。使用了QEvent后,构造的全通无线网络,在 128kbps的带宽下,还是要稍微顺畅一些。
相关文章:

Qt Signals Slots VS QEvents - Qt跨线程异步操作性能测试与选取建议
相关代码参考:https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/qt_event_signal 1.问题的由来 在对 taskBus 进行低延迟改造时,避免滥用信号与槽起到了较好的作用。笔者在前一篇文章中,叙述了通过避免广播式地播发信号&…...

Postgres 和 MySQL 应该怎么选?
PostgreSQL和MySQL是两个流行的关系型数据库管理系统(DBMS)。它们都具有一些相似的功能,但也有一些区别。 在选择使用哪个DBMS时,需要考虑多个因素,包括性能、可扩展性、安全性、功能丰富度、生态系统支持等。下面是对…...

【在英伟达nvidia的jetson-orin-nx和PC电脑ubuntu20.04上-装配ESP32开发调试环境-基础测试】
【在英伟达nvidia的jetson-orin-nx和PC电脑ubuntu20.04上-装配ESP32开发调试环境-基础测试】 1、概述2、实验环境3、 物品说明4、参考资料与自我总结5、实验过程1、创建目录2、克隆下载文件3、 拉取子目录安装和交叉编译工具链等其他工具4、添加环境变量6、将样例文件拷贝到桌面…...
我终于搞明白了HTTPS协议了!超长文章!
HTTPS协议是现代互联网中非常重要的一种安全协议,它能够在客户端和服务器之间建立一条安全的通信渠道,确保用户的隐私和数据安全。下面我来详细介绍HTTPS协议的相关知识。 HTTP协议的缺点 HTTP协议是互联网中的一种应用层协议,它负责客户端…...
Golang Testify介绍
简介 Golang是一种编译型语言,由Google开发,已经成为了Web开发领域中非常受欢迎的语言之一。在Golang生态系统中,有许多用于编写测试的框架和库,其中Testify是其中一个非常流行的测试框架。 Testify是一个用于编写测试的扩展包&…...

DALL·E 3怎么用?DALL·E 3如何申请开通 ?DALL·E 3如何免费使用?AI绘画教程来喽~
一、引言 DALLE 3 是 OpenAI 在上个月(2023 年 9 月)发布的一个文生图模型。 相对于 Midjourney 以及 Stable Diffusion,DALLE 3 最大的便利之处在于,用户不需要掌握 Prompt 的写法了,直接自然语言描述即可。 甚至还…...

安装 Dispatch 库
首先,我们需要安装 Dispatch 库。在命令行中运行以下命令来安装 Dispatch: $ sbt console然后,在 Scala 控制台中,导入所需的库: import dispatch._接下来,我们需要设置代理服务器。在 Dispatch 中&#…...

【Unity程序技巧】异步保险箱管理器
👨💻个人主页:元宇宙-秩沅 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 秩沅 原创 👨💻 收录于专栏:Uni…...

ChatGPT 助力英文论文翻译和润色
文章目录 一、前言二、主要内容1. 中英互译2. 中文润色3. 英文润色 三、总结 🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、前言 随着全球化的推进,跨文化交流变得越来越重要。在学术领域,英文论文的质量对于研究成果的传…...
【2024秋招】腾讯云智武汉后端开发一面 2023-9-20
1 java 1.1 hashMap 1.2 哈希冲突的解决方法 1.3 讲解一下CAS的aba问题 1.4 concurrentHashMap的并发方案为什么要使用cas ConcurrentHashMap 是 Java 并发包 java.util.concurrent 中的一个重要组件,用于提供高并发、高性能、线程安全的哈希映射。为了达到这样…...
k8s-----16、配置管理-ConfigMap
ConfigMap 1、作用2、以volume形式进行挂载2.1 创建配置文件2.2 创建ConfigMap文件2.3 最终的yaml文件 3、以变量形式进行挂载3.1 创建configmap文件3.2 书写最终yaml文件 1、作用 存储不加密的数据到etcd中,以变量或者volume形式挂载到pod的容器中场景:…...
QML QTP0001 not set 警告
使用QML的时候发现有这个警告。查阅资料之后发现解决办法。 大概的意思是说现在:/qt/qml/ 这个前缀是QML模块资源文件的前缀,而之前是:/ 这是从QT6.5开始的,旧的前缀被标记为废弃的。文档还说在使用qt_add_qml_module()不指定RESOURCE_PREFIX是新版的前…...

Mac M1编译 swift 5.8.1源码
参考链接:https://github.com/apple/swift/blob/main/docs/HowToGuides/GettingStarted.md#system-requirements 编译 Swift 5.8 源码-六虎 解决M1芯片的Homebrew安装问题--For M1使用者_m1 homebrew安装_a_52hz的博客-CSDN博客 建议全程梯子 一、检查和配置环境…...

[极客大挑战 2019]EasySQL
【解题思路】 1.打开靶机链接 2.输入数据进行尝试 输入1,1: 可以在导航栏里面看到username和password的变量。 3.使用万能密码 username:1 or 11# username:任意数据 password:任意数据 …...

统信UOS技术开放日:四大领域全面接入AI大模型能力
1024是程序员的节日,10月24日,统信举办2023统信UOS技术开放日暨deepin Meetup北京站活动,发布与大模型同行的UOS AI、浏览器AI助手、邮箱AI助手、自然语言全局搜索、畅写在线等多项最新AI技术与产品应用。 统信软件高级副总经理、CTO、深度社…...

【Linux系统编程:信号】产生信号 | 阻塞信号 | 处理信号 | 可重入函数
写在前面 通过学习信号可以理解进程与进程的一个相对关系,还能理解操作系统与进程的关系。要注意的是进程间通信中的信号量与这里的信号没有半毛钱关系,就像老婆和老婆饼。 本文要点: 掌握 Linux 信号的基本概念掌握信号产生的一般方式理解…...

Linux NFS的整体架构与核心代码解析
前面文章我们从应用层面对NFS进行了介绍,接下来的文章我们将进入实现层面。本文首先从整体上对Linux的NFS软件架构进行介绍,然后介绍代码与实际业务逻辑介绍一下NFS的处理流程。 NFS文件系统的架构分析 NFS分布式文件系统是一个客户端-服务端架构&#…...

28、Flink 的SQL之DROP 、ALTER 、INSERT 、ANALYZE 语句
Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...

正则表达式[总结]
文章目录 1. 为什么要学习正则表达式2. 再提出几个问题?3. 解决之道-正则表达式4. 正则表达式基本介绍5. 正则表达式底层实现(重要)6. 正则表达式语法6.1 基本介绍6.2 元字符(Metacharacter)-转义号 \\\6.3 元字符-字符匹配符6.4 元字符-选择匹配符6.5 元字符-限定符…...
【docker】搭建xxl-job
首先创建数据库,例如我已经有了mysql 在 192.168.20.17上 #首先要有对应的数据库,创建xxl-job所需表CREATE database if NOT EXISTS xxl_job default character set utf8mb4 collate utf8mb4_unicode_ci; use xxl_job;SET NAMES utf8mb4;CREATE TABLE xx…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...