局部静态变量实现的单例存在多个对象

文章目录

  • 背景
  • 测试代码
  • 运行测试
    • 尝试打开编译器优化
    • 进一步分析

背景

业务中出现日志打印失效,发现是因为管理日志对象的单例在运行过程中存在了多例的情况。下面通过还原业务场景来分析该问题。

测试代码

/* A.h */
#ifndef CALSS_A
#define CALSS_A#include <iostream>
#include <cstddef>
class A {
public:static A& GetInstance();void SetNum(size_t num);size_t GetNum();private:size_t m_num {0U};
};
#endif
/* A.cpp */
#include "A.h"
A& A::GetInstance()
{static A ins;std::cout << "A " << &ins << std::endl;return ins;
}void A::SetNum(size_t num)
{m_num = num;
}size_t A::GetNum()
{return m_num;
}
/* A2.h */
#ifndef CALSS_A_2
#define CALSS_A_2#include <iostream>
#include <cstddef>class A {
public:static A& GetInstance(){static A ins2;std::cout << "A2 " << &ins2 << std::endl;return ins2;}void SetNum(size_t num) {m_num = num;}size_t GetNum(){return m_num;}private:size_t m_num {0U};
};
#endif
/* B.h */
#ifndef CLASS_B
#define CLASS_B#include <cstddef>class B {
public:B();size_t GetNum();
};#endif
/* B.cpp */
#include "B.h"
#include "A2.h"B::B()
{A::GetInstance().SetNum(100U);
}size_t B::GetNum()
{return A::GetInstance().GetNum();
}
#include "A.h"
#include "B.h"
#include <iostream>int main()
{B b;A::GetInstance().SetNum(10U);std::cout << A::GetInstance().GetNum() << std::endl;std::cout << b.GetNum() << std::endl;return 0;
}

运行测试

通过简化的代码模拟业务中实际的依赖关系:头文件A.h中定义了类A,单例的实现在A.cpp中,生成动态库a;头文件A2.h中同样也定义了类A,单例的视线在头文件中,被B.cpp引用,生成动态库b;可执行文件a.out中会同时调用动态库a和动态库b中的接口,在实际业务中出现了多例的情况。
在这里插入图片描述

g++ A.cpp -I . -fpic -shared -o liba.so -O2
g++ B.cpp -I . -fpic -shared -o libb.so -O2
g++ main.cpp -L . -lb -la -I . -O2

运行结果显示,只存在单例,获取到的是A2.h中定义的对象(libb.so)。

A2 0x7fbcdfb19068
A2 0x7fbcdfb19068
A2 0x7fbcdfb19068
10
A2 0x7fbcdfb19068
10

调整二进制动态库链接的顺序,获取到的是A.cpp中定义的对象(liba.so)。从目前测试情况分析,不会出现多例的情况,但是具体使用的符号,跟动态库链接的顺序有关系,二进制中会使用先链接的动态库的符号

g++ main.cpp -L . -la -lb -I .

A 0x7f99ef74f058
A 0x7f99ef74f058
A 0x7f99ef74f058
10
A 0x7f99ef74f058
10

从符号表分析:使用readelf读取动态库和二进制的符号表,动态库b中既存在单例获取成员函数A::GetInstance()的弱符号,又存在全局唯一对象A::GetInstance()::ins2的符号。结合上述现象,先链接动态库b时,加载全局唯一对象A::GetInstance()::ins2的内存地址,后续获取到的单例都是该内存地址;后链接动态库b,弱符号A::GetInstance()会被a库中的强符号覆盖,因此获取到的单例是A.cpp中定义的对象。
readelf查看符号表-1

尝试打开编译器优化

前面证明链接时候的顺序不同,会加载不同内存地址的对象,但是在运行过程中还是单例。现在猜测运行过程中出现多例情况可能跟编译器的优化有关。因此,舱室打开编译的优化选项,重读上面的测试。

g++ A.cpp -I . -fpic -shared -o liba.so -O2
g++ B.cpp -I . -fpic -shared -o libb.so -O2
g++ main.cpp -L . -lb -la -I . -O2

运行结果显示,出现了多例的现象。

A2 0x7f019f611068
A 0x7f019f60c068
A 0x7f019f60c068
10
A2 0x7f019f611068
100

从符号表分析:与未打开编译器优化前最大的区别在于动态库b中单例获取成员函数A::GetInstance()的弱符号不见了,故动态库b中源码加载全局唯一对象A::GetInstance()::ins2的内存地址,动态库a中源码加载的是通过A::GetInstance()获取的对象的地址,两者地址不同。
因此,可以解释为什么在运行过程中出现了双例的情况。
readelf获取符号表-2

进一步分析

动态库b中单例获取成员函数A::GetInstance()的弱符号不见了的原因:

头文件中定义的函数,特别是内联函数和模板函数,在编译和链接过程中通常会被展开或优化掉,不会产生独立的符号。

无论是链接时会存在双例的情况,还是运行时会存在双例的情况,都是不符合预期的。因此,如何避免?
很简单,单例的实现放在cpp中。

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

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

相关文章

打造属于自己的脚手架工具并发布到npm仓库

一、创建项目 使用 npm init -y 创建项目创建项目入口文件 index.js在 package.json 中添加 bin 字段使用 npm link 命令将文件映射至全局&#xff0c;使可以在本地测试 zp 命令 // "zp" 为用于全局执行脚手架的命令&#xff0c;vue-cli中使用的是vue命令 "bi…

基于java+springboot+vue实现的旅游管理系统(文末源码+lw+ppt)23-402

研究的内容 当下流行的WPS、Word等办公软件成为了人们耳熟能详的系统&#xff0c;但一些更加专业性、性能更加强大的网络信息工具被人们“埋没”在互联网的大海中。甘肃旅游管理系统是一个便于用户查看热门景点、酒店信息、推荐线路、旅游攻略、景点资讯等&#xff0c;管理员进…

【Python基础篇】你了解python中运算符吗

文章目录 1. 算数运算符1.1 //整除1.2 %取模1.3 **幂 2. 赋值运算符3. 位运算符3.1 &&#xff08;按位与&#xff09;3.2 |&#xff08;按位或&#xff09;3.3 ^&#xff08;按位异或&#xff09;3.4 ~&#xff08;按位取反&#xff09;3.5 <<&#xff08;左移&#…

HTML 【实用教程】(2024最新版)

核心思想 —— 语义化 【面试题】如何理解 HTML 语义化 ?仅通过标签便能判断内容的类型&#xff0c;特别是区分标题、段落、图片和表格 增加代码可读性&#xff0c;让人更容易读懂对SEO更加友好&#xff0c;让搜索引擎更容易读懂 html 文件的基本结构 html 文件的文件后缀为 …

【高录用、快检索、过往5届均已检索、SPIE 出版】第六届无线通信与智能电网国际会议(ICWCSG 2024)

随着科技的飞速发展和能源需求的日益增长&#xff0c;智能电网技术逐渐成为电力行业的重要发展方向。与此同时&#xff0c;无线通信技术在近年来也取得了显著的进步&#xff0c;为智能电网的发展提供了强有力的支持。为了进一步推动无线通信与智能电网的结合与发展&#xff0c;…

学IT上培训班真的有用吗?

在学习IT技术的过程中&#xff0c;你是否也被安利过各种五花八门的技术培训班&#xff1f;这些培训班都是怎样向你宣传的&#xff0c;你又对此抱有着怎样的态度呢&#xff1f;在培训班里学技术&#xff0c;真的有用吗&#xff1f; 一、引入话题 IT行业是一个快速发展和不断变化…

C++初学者指南-4.诊断---未定义行为检测器

C初学者指南-4.诊断—未定义行为检测器 未定义行为检测器(UBSAN) 适用编译器&#xff1a;clang,g在运行时检测许多类型的未定义行为 解引用空指针从未对齐的指针读取整数溢出被0除 … 在代码中加入额外的指令:在调试构建中增加运行时约25% 示例&#xff1a;有符号整形溢出 …

Git在多人开发中的常见用例

前言 作为从一个 svn 转过来的 git 前端开发&#xff0c;在经历过git的各种便捷好处后&#xff0c;想起当时懵懂使用git的胆颤心惊&#xff1a;总是害怕用错指令&#xff0c;又或者遇到报错就慌的场景&#xff0c;想起当时查资料一看git指令这么多&#xff0c;看的头晕眼花&am…

深度学习原理与Pytorch实战

深度学习原理与Pytorch实战 第2版 强化学习人工智能神经网络书籍 python动手学深度学习框架书 TransformerBERT图神经网络&#xff1a; 技术讲解 编辑推荐 1.基于PyTorch新版本&#xff0c;涵盖深度学习基础知识和前沿技术&#xff0c;由浅入深&#xff0c;通俗易懂&#xf…

家里老人能操作的电视直播软件,目前能用的免费看直播的电视软件app,适合电视和手机使用!

2024年许多能看电视直播的软件都不能用了&#xff0c;家里的老人也不会手机投屏&#xff0c;平时什么娱乐都没有了&#xff0c;这真的太不方便了。 很多老人并不喜欢去买一个广电的机顶盒&#xff0c;或者花钱拉有线电视。 现在的电视大多数都是智能电视&#xff0c;所以许多电…

Redis基本命令源码解析-字符串命令

1. set 用于将kv设置到数据库中 2. mset 批量设置kv mset (msetnx) key1 value1 key2 value2 ... mset:msetCommand msetnx:msetnxCommand msetCommand和msetnxCommand都调用msetGenericCommand 2.1 msetGenericCommand 如果参数个数为偶数,则响应参数错误并返回 如果…

【项目日记(一)】梦幻笔耕-数据层实现

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多项目内容 目录 1.前言2.后端模块3数据库设计4.mapper实现4.1UserInfoMapper4.2BlogMapper 5.总结 1.…

硬件开发笔记(二十四):贴片电容的类别、封装介绍,AD21导入贴片电容、原理图和封装库3D模型

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140241817 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

存储结构与管理磁盘

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、一切从“/”开始 二、物理设备的命名规则 三、文件系统与数据资料 四、挂载硬件设备 五、添加硬盘设备 六、添加交换分区 七、磁盘容…

如何在 PostgreSQL 中实现数据的增量备份和恢复?

文章目录 一、增量备份的原理二、准备工作&#xff08;一&#xff09;环境配置&#xff08;二&#xff09;创建测试数据库和表&#xff08;三&#xff09;插入初始数据 三、全量备份四、基于时间点的增量备份&#xff08;一&#xff09;开启 WAL 归档&#xff08;二&#xff09…

政策公告与提醒

自 2024 年 4 月 3 日起,您将至少有 30 天的时间来更新应用,使其符合下方所述的政策变更。 我们将推出“儿童安全标准”政策,规定社交应用和约会交友应用必须遵循特定标准,并在 Play 管理中心内以自行认证的形式证明合规后才能发布。 为了提高健康相关应用在 Google Play…

docker 重要且常用命令大全

本文将总结一些常见的重要的docker命令&#xff0c;以作备忘。后续如果有新的比较常用重要的也会更新进来。欢迎补充。 docker服务管理 首先我们要解释一下&#xff1a;systemctl和docker命令的不同 systemctl&#xff1a;是许多 Linux 发行版中默认的初始化系统和服务管理器。…

11.常见的Bean后置处理器

CommonAnnotationBeanPostProcessor (Resource PostConstructor PreDestroy) AutowiredAnnotationBeanPostProcessor (Autowired Value) GenericApplicationContext是一个干净的容器&#xff0c;它没有添加任何的PostProcessor处理器。 调用GenericApplicationContext.refre…

赛元单片机开发工具SOC_Programming_Tool_Enhance_V1.50 分享

下载地址&#xff1a; SOC_Programming_Tool_Enhance_V1.50(LIB0D30).rar: https://545c.com/f/45573183-1320016694-557ebd?p7526 (访问密码: 7526)

docker中实现多机redis主从集群

redis主从集群是每个使用redis的小伙伴都必需知道的&#xff0c;那如何在docker中快速配置呢&#xff1f;这篇来教你快速上手&#xff0c;跟着复制完全就能用&#xff01;&#xff01; 1. 前置准备 1.1 docker安装 以防有小伙伴没预先安装docker&#xff0c;这里提供安装步骤…