从单例模式说动态链接

为了保证软件中某个变量的访问唯一性,大神设计了一种模式叫做单例模式。但是单例模式真的能保证访问唯一性吗?其实不然,下面我们就来复现这个场景:

cpp 复制代码
//---------------singleton.hpp----------------------
#ifndef SINGLETON_H
#define SINGLETON_H

#include <iostream>
#include <string>
#include <memory>

// 不安全的单例实现 - 在头文件中定义
class UnsafeSingleton {
public:
    static UnsafeSingleton& getInstance() {
        static UnsafeSingleton instance;  // 每个包含此文件的模块都有自己的实例
        return instance;
    }
    
    void setValue(int val) { 
        value = val; 
        std::cout << "[" << module_name << "] Set value to: " << val 
                  << " (address: " << this << ")" << std::endl;
    }
    
    int getValue() const { 
        std::cout << "[" << module_name << "] Get value: " << value 
                  << " (address: " << this << ")" << std::endl;
        return value; 
    }
    
    void setModuleName(const std::string& name) {
        module_name = name;
    }
    
    const std::string& getModuleName() const {
        return module_name;
    }
    
private:
    UnsafeSingleton() : value(0), module_name("Unknown") {
        std::cout << "UnsafeSingleton constructed at: " << this << std::endl;
    }
    
    ~UnsafeSingleton() {
        std::cout << "UnsafeSingleton destroyed at: " << this << std::endl;
    }
    
    int value;
    std::string module_name;
    
    // 禁止拷贝
    UnsafeSingleton(const UnsafeSingleton&) = delete;
    UnsafeSingleton& operator=(const UnsafeSingleton&) = delete;
};
//---------------singleton.hpp----------------------

//---------------shared_lib.cpp----------------------
#include "singleton.hpp"
#include <iostream>

// 动态库导出的函数
extern "C" {
    void initialize_library_singleton() {
        std::cout << "=== 动态库: 初始化单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        instance.setModuleName("SharedLib");
        instance.setValue(100);
    }
    
    void modify_library_singleton(int new_value) {
        std::cout << "=== 动态库: 修改单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        instance.setValue(new_value);
    }
    
    void print_library_singleton() {
        std::cout << "=== 动态库: 打印单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        std::cout << "模块: " << instance.getModuleName() 
                  << ", 值: " << instance.getValue() 
                  << ", 地址: " << &instance << std::endl;
    }
    
    // 返回单例地址的函数
    void* get_singleton_address() {
        return &UnsafeSingleton::getInstance();
    }
}

#endif
//---------------shared_lib.cpp----------------------

//---------------main.cpp----------------------
#include "singleton.hpp"
#include <iostream>
#include <dlfcn.h>  // 动态加载库
#include <cassert>

// 声明动态库函数
typedef void (*lib_func_t)();
typedef void (*lib_func_int_t)(int);
typedef void* (*lib_func_addr_t)();

int main() {
    std::cout << "=== 程序启动 ===" << std::endl;
    
    // 第一步:在可执行文件中初始化单例
    std::cout << "\n=== 步骤1: 可执行文件中初始化 ===" << std::endl;
    auto& exe_instance = UnsafeSingleton::getInstance();
    exe_instance.setModuleName("Executable");
    exe_instance.setValue(42);
    
    std::cout << "可执行文件单例地址: " << &exe_instance << std::endl;
    
    // 第二步:动态加载库
    std::cout << "\n=== 步骤2: 加载动态库 ===" << std::endl;
    void* lib_handle = dlopen("./libshared_lib.so", RTLD_LAZY);
    if (!lib_handle) {
        std::cerr << "无法加载动态库: " << dlerror() << std::endl;
        return 1;
    }
    
    // 获取库函数指针
    auto init_func = (lib_func_t)dlsym(lib_handle, "initialize_library_singleton");
    auto modify_func = (lib_func_int_t)dlsym(lib_handle, "modify_library_singleton");
    auto print_func = (lib_func_t)dlsym(lib_handle, "print_library_singleton");
    auto addr_func = (lib_func_addr_t)dlsym(lib_handle, "get_singleton_address");
    
    if (!init_func || !modify_func || !print_func || !addr_func) {
        std::cerr << "无法获取函数符号: " << dlerror() << std::endl;
        dlclose(lib_handle);
        return 1;
    }
    
    // 第三步:在动态库中操作单例
    std::cout << "\n=== 步骤3: 动态库中操作 ===" << std::endl;
    init_func();        // 库中初始化
    modify_func(200);   // 库中修改
    print_func();       // 库中打印
    
    // 第四步:比较地址
    std::cout << "\n=== 步骤4: 比较地址 ===" << std::endl;
    void* lib_singleton_addr = addr_func();
    std::cout << "可执行文件单例地址: " << &exe_instance << std::endl;
    std::cout << "动态库单例地址:     " << lib_singleton_addr << std::endl;
    std::cout << "是否相同? " << (lib_singleton_addr == &exe_instance ? "是" : "否") << std::endl;
    
    // 第五步:验证可执行文件中的值
    std::cout << "\n=== 步骤5: 验证可执行文件中的值 ===" << std::endl;
    std::cout << "可执行文件单例的值: " << exe_instance.getValue() << std::endl;
    std::cout << "可执行文件单例模块: " << exe_instance.getModuleName() << std::endl;
    
    // 第六步:再次修改和验证
    std::cout << "\n=== 步骤6: 再次修改验证 ===" << std::endl;
    std::cout << "在可执行文件中设置值为 300" << std::endl;
    exe_instance.setValue(300);
    
    std::cout << "\n在动态库中查看值:" << std::endl;
    print_func();
    
    // 清理
    dlclose(lib_handle);
    
    std::cout << "\n=== 程序结束 ===" << std::endl;
    return 0;
}
//---------------main.cpp----------------------
bash 复制代码
#---------------CMakeLists.txt----------------------
# === 文件: CMakeLists.txt ===
cmake_minimum_required(VERSION 3.10)
project(singleton_issue)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 创建动态库
add_library(shared_lib SHARED shared_lib.cpp)

# 创建可执行文件
add_executable(main_exe main.cpp)

# 链接动态库
target_link_libraries(main_exe ${CMAKE_DL_LIBS})
#---------------CMakeLists.txt----------------------

#---------------run.sh----------------------
echo "编译项目..."
mkdir -p build
cd build
cmake ..
make

echo -e "\n=== 运行程序 ==="
./main_exe

echo -e "\n=== 查看符号表 ==="
echo "动态库符号:"
nm -C libshared_lib.so | grep UnsafeSingleton

echo -e "\n可执行文件符号:"
nm -C main_exe | grep UnsafeSingleton
#---------------run.sh----------------------

最终运行结果为:

bash 复制代码
=== 程序启动 ===

=== 步骤1: 可执行文件中初始化 ===
UnsafeSingleton constructed at: 0x55a1b2c0c040
[Unknown] Set value to: 42 (address: 0x55a1b2c0c040)
可执行文件单例地址: 0x55a1b2c0c040

=== 步骤2: 加载动态库 ===

=== 步骤3: 动态库中操作 ===
=== 动态库: 初始化单例 ===
UnsafeSingleton constructed at: 0x7f8b3d5a7040
[Unknown] Set value to: 100 (address: 0x7f8b3d5a7040)
=== 动态库: 修改单例 ===
[SharedLib] Set value to: 200 (address: 0x7f8b3d5a7040)
=== 动态库: 打印单例 ===
模块: SharedLib, 值: 200, 地址: 0x7f8b3d5a7040

=== 步骤4: 比较地址 ===
可执行文件单例地址: 0x55a1b2c0c040
动态库单例地址:     0x7f8b3d5a7040
是否相同? 否

=== 步骤5: 验证可执行文件中的值 ===
[Executable] Get value: 42 (address: 0x55a1b2c0c040)
可执行文件单例的值: 42
可执行文件单例模块: Executable

=== 步骤6: 再次修改验证 ===
在可执行文件中设置值为 300
[Executable] Set value to: 300 (address: 0x55a1b2c0c040)

在动态库中查看值:
=== 动态库: 打印单例 ===
模块: SharedLib, 值: 200, 地址: 0x7f8b3d5a7040

=== 程序结束 ===

从上面的例子我们可以发现,如果动态链接库和可执行文件种都使用了自以为相同的单例,可是实际上却不是同一个。原因是可执行文件和动态链接库是单独编译生成的,他们并不知道彼此都包含了同一块代码。

bash 复制代码
动态库符号:
0000000000005128 u guard variable for UnsafeSingleton::getInstance()::instance
0000000000002692 W UnsafeSingleton::getInstance()
00000000000028c4 W UnsafeSingleton::setModuleName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
0000000000002744 W UnsafeSingleton::setValue(int)
0000000000002908 W UnsafeSingleton::UnsafeSingleton()
0000000000002908 W UnsafeSingleton::UnsafeSingleton()
0000000000002a04 W UnsafeSingleton::~UnsafeSingleton()
0000000000002a04 W UnsafeSingleton::~UnsafeSingleton()
00000000000028f2 W UnsafeSingleton::getModuleName[abi:cxx11]() const
0000000000002806 W UnsafeSingleton::getValue() const
0000000000005100 u UnsafeSingleton::getInstance()::instance

可执行文件符号:
00000000000042a8 u guard variable for UnsafeSingleton::getInstance()::instance
00000000000019d7 W UnsafeSingleton::getInstance()
0000000000001c06 W UnsafeSingleton::setModuleName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
0000000000001a86 W UnsafeSingleton::setValue(int)
0000000000001c4a W UnsafeSingleton::UnsafeSingleton()
0000000000001c4a W UnsafeSingleton::UnsafeSingleton()
0000000000001d46 W UnsafeSingleton::~UnsafeSingleton()
0000000000001d46 W UnsafeSingleton::~UnsafeSingleton()
0000000000001c34 W UnsafeSingleton::getModuleName[abi:cxx11]() const
0000000000001b48 W UnsafeSingleton::getValue() const
0000000000004280 u UnsafeSingleton::getInstance()::instance

通过查看上面符号表我们可以发现可执行文件和动态库中都有UnsafeSingleton::getInstance()::instance,并且都是u弱符号且在不同的地址。这说明了可执行文件和动态库中都保存了一份定义,并且相互独立。

下面修复一下这个问题

cpp 复制代码
//---------------singleton.hpp----------------------
#ifndef SINGLETON_H
#define SINGLETON_H

#include <iostream>
#include <string>
#include <memory>

// 不安全的单例实现 - 在头文件中定义
class UnsafeSingleton {
public:
    static UnsafeSingleton& getInstance() ;
    
    void setValue(int val) { 
        value = val; 
        std::cout << "[" << module_name << "] Set value to: " << val 
                  << " (address: " << this << ")" << std::endl;
    }
    
    int getValue() const { 
        std::cout << "[" << module_name << "] Get value: " << value 
                  << " (address: " << this << ")" << std::endl;
        return value; 
    }
    
    void setModuleName(const std::string& name) {
        module_name = name;
    }
    
    const std::string& getModuleName() const {
        return module_name;
    }
    
private:
    UnsafeSingleton() : value(0), module_name("Unknown") {
        std::cout << "UnsafeSingleton constructed at: " << this << std::endl;
    }
    
    ~UnsafeSingleton() {
        std::cout << "UnsafeSingleton destroyed at: " << this << std::endl;
    }
    
    int value;
    std::string module_name;
    
    // 禁止拷贝
    UnsafeSingleton(const UnsafeSingleton&) = delete;
    UnsafeSingleton& operator=(const UnsafeSingleton&) = delete;
};
//---------------singleton.hpp----------------------


//---------------singleton.cpp----------------------
#include "singleton.hpp"

UnsafeSingleton& UnsafeSingleton::getInstance() {
        static UnsafeSingleton instance;  // 每个包含此文件的模块都有自己的实例
        return instance;
    }
//---------------singleton.cpp----------------------


//---------------shared_lib.cpp----------------------
#include "singleton.hpp"
#include <iostream>

// 动态库导出的函数
extern "C" {
    void initialize_library_singleton() {
        std::cout << "=== 动态库: 初始化单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        instance.setModuleName("SharedLib");
        instance.setValue(100);
    }
    
    void modify_library_singleton(int new_value) {
        std::cout << "=== 动态库: 修改单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        instance.setValue(new_value);
    }
    
    void print_library_singleton() {
        std::cout << "=== 动态库: 打印单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        std::cout << "模块: " << instance.getModuleName() 
                  << ", 值: " << instance.getValue() 
                  << ", 地址: " << &instance << std::endl;
    }
    
    // 返回单例地址的函数
    void* get_singleton_address() {
        return &UnsafeSingleton::getInstance();
    }
}

#endif
//---------------shared_lib.cpp----------------------

//---------------main.cpp----------------------
#include "singleton.hpp"
#include <iostream>
#include <dlfcn.h>  // 动态加载库
#include <cassert>

// 声明动态库函数
typedef void (*lib_func_t)();
typedef void (*lib_func_int_t)(int);
typedef void* (*lib_func_addr_t)();

int main() {
    std::cout << "=== 程序启动 ===" << std::endl;
    
    // 第一步:在可执行文件中初始化单例
    std::cout << "\n=== 步骤1: 可执行文件中初始化 ===" << std::endl;
    auto& exe_instance = UnsafeSingleton::getInstance();
    exe_instance.setModuleName("Executable");
    exe_instance.setValue(42);
    
    std::cout << "可执行文件单例地址: " << &exe_instance << std::endl;
    
    // 第二步:动态加载库
    std::cout << "\n=== 步骤2: 加载动态库 ===" << std::endl;
    void* lib_handle = dlopen("./libshared_lib.so", RTLD_LAZY);
    if (!lib_handle) {
        std::cerr << "无法加载动态库: " << dlerror() << std::endl;
        return 1;
    }
    
    // 获取库函数指针
    auto init_func = (lib_func_t)dlsym(lib_handle, "initialize_library_singleton");
    auto modify_func = (lib_func_int_t)dlsym(lib_handle, "modify_library_singleton");
    auto print_func = (lib_func_t)dlsym(lib_handle, "print_library_singleton");
    auto addr_func = (lib_func_addr_t)dlsym(lib_handle, "get_singleton_address");
    
    if (!init_func || !modify_func || !print_func || !addr_func) {
        std::cerr << "无法获取函数符号: " << dlerror() << std::endl;
        dlclose(lib_handle);
        return 1;
    }
    
    // 第三步:在动态库中操作单例
    std::cout << "\n=== 步骤3: 动态库中操作 ===" << std::endl;
    init_func();        // 库中初始化
    modify_func(200);   // 库中修改
    print_func();       // 库中打印
    
    // 第四步:比较地址
    std::cout << "\n=== 步骤4: 比较地址 ===" << std::endl;
    void* lib_singleton_addr = addr_func();
    std::cout << "可执行文件单例地址: " << &exe_instance << std::endl;
    std::cout << "动态库单例地址:     " << lib_singleton_addr << std::endl;
    std::cout << "是否相同? " << (lib_singleton_addr == &exe_instance ? "是" : "否") << std::endl;
    
    // 第五步:验证可执行文件中的值
    std::cout << "\n=== 步骤5: 验证可执行文件中的值 ===" << std::endl;
    std::cout << "可执行文件单例的值: " << exe_instance.getValue() << std::endl;
    std::cout << "可执行文件单例模块: " << exe_instance.getModuleName() << std::endl;
    
    // 第六步:再次修改和验证
    std::cout << "\n=== 步骤6: 再次修改验证 ===" << std::endl;
    std::cout << "在可执行文件中设置值为 300" << std::endl;
    exe_instance.setValue(300);
    
    std::cout << "\n在动态库中查看值:" << std::endl;
    print_func();
    
    // 清理
    dlclose(lib_handle);
    
    std::cout << "\n=== 程序结束 ===" << std::endl;
    return 0;
}
//---------------main.cpp----------------------
bash 复制代码
#---------------CMakeLists.txt----------------------
cmake_minimum_required(VERSION 3.10)
project(singleton_issue)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 创建动态库
add_library(shared_lib SHARED shared_lib.cpp singleton.cpp)

# 创建可执行文件
add_executable(main_exe main.cpp)

# 链接动态库
target_link_libraries(main_exe shared_lib)
#---------------CMakeLists.txt----------------------

#---------------run.sh----------------------
echo "编译项目..."
mkdir -p build
cd build
cmake ..
make

echo -e "\n=== 运行程序 ==="
./main_exe

echo -e "\n=== 查看符号表 ==="
echo "动态库符号:"
nm -C libshared_lib.so | grep UnsafeSingleton

echo -e "\n可执行文件符号:"
nm -C main_exe | grep UnsafeSingleton
#---------------run.sh----------------------

运行结果如下

bash 复制代码
=== 运行程序 ===
=== 程序启动 ===

=== 步骤1: 可执行文件中初始化 ===
UnsafeSingleton constructed at: 0x7236c17c1120
[Executable] Set value to: 42 (address: 0x7236c17c1120)
可执行文件单例地址: 0x7236c17c1120

=== 步骤2: 加载动态库 ===

=== 步骤3: 动态库中操作 ===
=== 动态库: 初始化单例 ===
[SharedLib] Set value to: 100 (address: 0x7236c17c1120)
=== 动态库: 修改单例 ===
[SharedLib] Set value to: 200 (address: 0x7236c17c1120)
=== 动态库: 打印单例 ===
模块: SharedLib, 值: [SharedLib] Get value: 200 (address: 0x7236c17c1120)
200, 地址: 0x7236c17c1120

=== 步骤4: 比较地址 ===
可执行文件单例地址: 0x7236c17c1120
动态库单例地址:     0x7236c17c1120
是否相同? 是

=== 步骤5: 验证可执行文件中的值 ===
可执行文件单例的值: [SharedLib] Get value: 200 (address: 0x7236c17c1120)
200
可执行文件单例模块: SharedLib

=== 步骤6: 再次修改验证 ===
在可执行文件中设置值为 300
[SharedLib] Set value to: 300 (address: 0x7236c17c1120)

在动态库中查看值:
=== 动态库: 打印单例 ===
模块: SharedLib, 值: [SharedLib] Get value: 300 (address: 0x7236c17c1120)
300, 地址: 0x7236c17c1120

=== 程序结束 ===
UnsafeSingleton destroyed at: 0x7236c17c1120

=== 查看符号表 ===
动态库符号:
0000000000005148 b guard variable for UnsafeSingleton::getInstance()::instance
0000000000002856 T UnsafeSingleton::getInstance()
0000000000002812 W UnsafeSingleton::setModuleName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
0000000000002692 W UnsafeSingleton::setValue(int)
0000000000002974 W UnsafeSingleton::UnsafeSingleton()
0000000000002974 W UnsafeSingleton::UnsafeSingleton()
0000000000002a70 W UnsafeSingleton::~UnsafeSingleton()
0000000000002a70 W UnsafeSingleton::~UnsafeSingleton()
0000000000002840 W UnsafeSingleton::getModuleName[abi:cxx11]() const
0000000000002754 W UnsafeSingleton::getValue() const
0000000000005120 b UnsafeSingleton::getInstance()::instance

可执行文件符号:
                 U UnsafeSingleton::getInstance()
0000000000001b18 W UnsafeSingleton::setModuleName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
0000000000001998 W UnsafeSingleton::setValue(int)
0000000000001b46 W UnsafeSingleton::getModuleName[abi:cxx11]() const
0000000000001a5a W UnsafeSingleton::getValue() const

通过可执行文件和动态库的符号表可以发现,只有动态库有UnsafeSingleton::getInstance()::instance的符号,并且是b。

这里介绍符号重定向的问题,以及强符号弱符号的问题。

常见符号类型:

bash 复制代码
大写字母 = 强符号
小写字母 = 弱符号

# 常见符号类型
W 弱符号(未定义)
w 弱符号(已定义)
V 弱符号
v 弱符号

T 强符号(代码段)
t 弱符号(代码段)
D 强符号(数据段)
d 弱符号(数据段)
B 强符号(BSS段)
b 弱符号(BSS段)
相关推荐
你撅嘴真丑5 小时前
STL练习
开发语言·c++·算法
bybitq5 小时前
cmake构建c++项目时,vscode/cursor无法识别头文件路径,导致报错,解决方案
开发语言·c++·vscode
无限进步_6 小时前
二叉搜索树(BST)详解:从原理到实现
开发语言·数据结构·c++·ide·后端·github·visual studio
wangjialelele6 小时前
二刷C语言后,一万字整理细碎知识点
c语言·开发语言·数据结构·c++·算法·cpp
mjhcsp6 小时前
P3145 [USACO16OPEN] Splitting the Field G(题解)
开发语言·c++·算法
空空潍6 小时前
hot100-合并区间(day14)
c++·算法·leetcode
是娇娇公主~6 小时前
算法——【最大子数组和】
数据结构·c++·算法
XH华6 小时前
备战蓝桥杯,第一章:C++入门
c++·蓝桥杯
Sheep Shaun6 小时前
深入理解AVL树:从概念到完整C++实现详解
服务器·开发语言·数据结构·c++·后端·算法