在 C++11 代码中,我有一个带有重要构造函数和析构函数(分配内存)的静态变量:
// in logger.h
class logger {
public:
class log_data
{
// log_data allocates and frees memory during construction/destruction
}
static log_data l;
};
// in logger.cpp
logger::log_data logger::l;
然后,logger.cpp 被编译为共享库和链接到共享库本身的可执行文件。我希望链接器能够识别 logger::l 被编译了两次并“合并”两个副本。
问题是,在运行时,这就是我认为正在发生的事情:
我很确定是这样,因为:
我的问题是:有没有办法告诉编译器,无论编译了多少个库或可执行文件,静态变量都只能构造和析构一次?还有其他解决办法吗?
我尝试从可执行编译中删除 logger.cpp,并将其仅与 1 个包含 logger.cpp 的共享库链接。它可以正常工作,但问题仍然存在,因为 logger.cpp 可以包含在多个共享库中,所有共享库都由同一个可执行文件使用
谢谢你
可执行文件和共享库都将获得自己的
logger::log_data logger::l;
静态变量。您可以使用nm
之类的工具来确认这一点。
我已经使用以下内容设置了基本的本地复制:
// foo.hpp
void foo();
// foo.cpp
#include "foo.hpp"
void foo() {}
// log.hpp
#include <iostream>
class logger {
public:
logger() {
std::cout<<"logger()"<<std::endl;
}
~logger() {
std::cout<<"~logger()"<<std::endl;
}
void log() {}
};
extern logger logger_global;
// log.cpp
#include "log.hpp"
logger logger_global;
// log_internal.cpp
#include "log.hpp"
void logger::log() {}
// main.cpp
#include <iostream>
#include "log.hpp"
#include "foo.hpp"
int main() {
std::cout<<"main "<<std::endl;
logger_global.log();
foo();
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(test LANGUAGES C CXX)
add_library(foo SHARED log.cpp foo.cpp)
add_executable(exec main.cpp log.cpp)
target_link_libraries(exec PUBLIC foo)
运行可执行文件:
$ ./exec
logger()
logger()
main
~logger()
~logger()
看纳米:
$ nm exec | grep logger_global
0000000000001244 t _GLOBAL__sub_I_logger_global
0000000000004151 B logger_global
$ nm libfoo.so | grep logger_global
0000000000004031 B logger_global
可执行文件和库都有自己的
logger_global
,并且各自进行自己的静态构造和销毁。与静态库不同,链接器不能仅从共享库中获取所需的内容。整个共享库必须加载到内存中,并且作为其静态数据的一部分必须进行初始化。在调试器中运行,您可以验证是否使用相同的 logger::logger
指针调用了两次 this
。
您可能想知道
_GLOBAL__sub_I_logger_global
是什么以及为什么只有可执行文件才有它。该函数由编译器自动生成,并对全局记录器执行静态初始化。共享库实际上也有一个,但是,它有一个不同的名称:_GLOBAL__sub_I_log.cpp
。
该怎么办?
您应该将通用记录器代码提取到其自己的库中,并使您的库和可执行文件链接到记录器库。