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

news2024/11/26 4:34:05

文章目录

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

背景

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

测试代码

/* 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.coloradmin.cn/o/1903835.html

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

相关文章

Docker 容器网络及其配置说明

Docker 容器网络及其配置说明 docker容器网络docker的4种网络模式bridge 模式container模式host 模式none 模式应用场景 docker 容器网络配置Linux 内核实现名称空间的创建创建 Network Namespace操作 Network Namespace 转移设备veth pair创建 veth pair实现 Network Namespac…

nature methods | 11种空间转录组学技术的系统性比较

—DOI: 10.1038/s41592-024-02325-3 Systematic comparison of sequencing-based spatial transcriptomic methods 学习了一下空间转录组技术怎么做benchmark&#xff0c;从多个的角度去考虑目前技术的性能&#xff0c;受益良多。但该研究缺少对10X Visium HD的测评&#xff…

mac怎么压缩pdf文件大小,mac压缩pdf文件大小不改变清晰度

在数字化时代&#xff0c;pdf格式因其良好的兼容性和稳定性&#xff0c;成为了文档分享和传输的首选。然而&#xff0c;随着文件内容的丰富&#xff0c;pdf文件的体积也越来越大&#xff0c;给存储和传输带来了不小的困扰。本文将揭秘几种简单有效的pdf文件压缩方法&#xff0c…

python爬虫入门(一)之HTTP请求和响应

一、爬虫的三个步骤&#xff08;要学习的内容&#xff09; 1、获取网页内容 &#xff08;HTTP请求、Requests库&#xff09; 2、解析网页内容 &#xff08;HTML网页结构、Beautiful Soup库&#xff09; 3、存储或分析数据 b站学习链接&#xff1a; 【【Python爬虫】爆肝两…

Vue3基础知识:组合式API中的provide和inject,他们作用是什么?如何使用?以及案例演示

1.provide和inject相较于父子传递的不同在于provide,inject可以用于跨层级通信&#xff08;通俗易懂的讲就是可以实现爷孙之间的直接信息传递&#xff09;。 1.跨层级传递数据 1.在顶层组件通过provide函数提供数据 2.底层组件通过inject函数获取数据 演示一&#xff1a;跨…

vCenter登录失败报500错误:no healthy upstream

过了个周末登录vCenter的时候提示&#xff1a;HTTP状态500 - 内部服务器错误&#xff1b;重启服务后提示&#xff1a;no healthy upstream。如下图&#xff1a; 看到这个情况&#xff0c;肯定就是部分不服务异常了或者压根就没有启动。至于说因为啥异常还不得而知。想着登录管理…

MSPM0G3507——串口0从数据线传输变为IO口传输

默认的跳线帽时这样的&#xff0c;这样时是数据线传输 需要改成这样&#xff0c;即可用IO口进行数据传输

Spring IOC基于XML和注解管理Bean

IoC 是 Inversion of Control 的简写&#xff0c;译为“ 控制反转 ”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出 松耦合、更优良的程序。 Spring 通过 IoC 容器来管理所有 Java 对象…

【国产AI绘图】快手把“可图”大模型开源了,这是一款支持中文的SDXL模型

Kolors 是由 Kuaishou Kolors 团队&#xff08;快手可图&#xff09;开发的基于潜在扩散的大规模文本到图像生成模型。经过数十亿对文本图像的训练&#xff0c;Kolors 在视觉质量、复杂语义的准确性以及中英文字符的文本渲染方面&#xff0c;与开源和专有模型相比都具有显著优势…

【LInux】从动态库的加载深入理解页表机制

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

YOLOv8改进---BiFPN特征融合

一、BiFPN原理 1.1 基本原理 BiFPN&#xff08;Bidirectional Feature Pyramid Network&#xff09;&#xff0c;双向特征金字塔网络是一种高效的多尺度特征融合网络&#xff0c;其基本原理概括分为以下几点&#xff1a; 双向特征融合&#xff1a;BiFPN允许特征在自顶向下和自…

DAY21-力扣刷题

1.买卖股票的最佳时机 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int[] prices) {int minpriceInteger.MAX_VALUE;int maxprofit0;for(int i0;i<prices.length;i){if(prices[i]<minprice){minpriceprices[…

【面试八股文】java基础知识

引言 本文是java面试时的一些常见知识点总结归纳和一些拓展&#xff0c;笔者在学习这些内容时&#xff0c;特地整理记录下来&#xff0c;以供大家学习共勉。 一、数据类型 1.1 为什么要设计封装类&#xff0c;Integer和int区别是什么&#xff1f; 使用封装类的目的 对象化:…

Webpack安装以及快速入门

3 Webpack 1 什么是Webpack https://webpack.js.org/ (官网) webpack 是一个现代 javascript 应用程序的 静态模块打包器 (module bundler) 待会要学的 vue-cli 脚手架环境, 集成了 webpack, 所以才能对各类文件进行打包处理 webpack是一个 静态模块 打包器,可以做以下的这…

数据自动备份方法分享!

现在很多朋友对于第三方软件颇为青睐&#xff0c;因为它们具备许多电脑自带备份工具所不具备的功能。例如&#xff0c;自动备份数据的需求。尽管你已经备份了电脑数据&#xff0c;但日常使用中数据常会增加&#xff0c;你可能无暇顾及每天的备份工作。因此&#xff0c;使用数据…

C++ 引用做函数返回值

作用&#xff1a;引用是可以作为函数的返回值存在的 注意&#xff1a;不要返回局部变量引用 用法&#xff1a;函数调用作为左值 示例&#xff1a; 运行结果&#xff1a;

cs231n作业1——KNN

参考文章&#xff1a;assignment1——KNN KNN 测试时分别计算测试样本和训练集中的每个样本的距离&#xff0c;然后选取距离最近的k个样本的标签信息来进行分类。 方法1&#xff1a;Two Loops for i in range(num_test):for j in range(num_train):dist X[i, :] - self.X…

昇思25天学习打卡营第19天 | RNN实现情感分类

RNN实现情感分类 概述 情感分类是自然语言处理中的经典任务&#xff0c;是典型的分类问题。本节使用MindSpore实现一个基于RNN网络的情感分类模型&#xff0c;实现如下的效果&#xff1a; 输入: This film is terrible 正确标签: Negative 预测标签: Negative输入: This fil…

Vue3+.NET6前后端分离式管理后台实战(二十八)

1&#xff0c;Vue3.NET6前后端分离式管理后台实战(二十八)

初阶数据结构 二叉树常用函数(二)

函数一 求二叉树第K层的节点个数 还是一样 我们假设 K就是等于一 如果说是一个空数的话就返回0 如果说有值的话就返回一个1就可以 假设这个这层既不为空 又不是第K层的话 那么就说明第K层肯定是子树下面 那么就说明是左右子树的第&#xff08;K-1&#xff09;层 那么只将…