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