我有一些遗留代码,其中包含一些使用全局变量的构造函数注册自身的单例类。这是一个很大的代码库,被编译成一个可执行文件。我尝试组织代码库并将代码重新分组到库中。当前代码的一个最小示例是
主程序
int main(int argc, char *argv[])
{
return 0;
}
哈希.cpp
#include <iostream>
class Hash
{
public:
Hash()
{
std::cout << "Hash\n";
}
};
Hash a;
当前的构建配置是
CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
project(mcve)
add_executable(mcve main.cpp Hash.cpp)
构建代码并运行可执行文件
Hash
我已将构建配置修改为
cmake_minimum_required(VERSION 3.26)
project(mcve)
add_library(Hash Hash.cpp)
add_executable(mcve main.cpp)
target_link_libraries(mcve Hash)
这将创建一个静态库libHash.a
并将其链接到可执行文件。编译相同的代码并运行可执行文件不会打印任何内容。为什么会出现这种差异?在哪里进行了描述?它是 C++ 标准的一部分还是编译器的一部分?它是特定于操作系统的(Linux 静态库)吗?这是未定义的行为吗?
我想,应该在链接器的文档以及介绍性教科书中描述这种差异,解释什么是静态库、它们如何工作以及如何使用它们。
图片中没有静态库:当翻译单元显式链接在一起时,其中的所有内容都成为可执行文件的一部分。
在第一个示例中,
main.cpp
和hash.cpp
都链接到可执行文件中,并且在启动时构造mcve
唯一的全局对象。hash.cpp
结束。我再说一遍,与静态库的链接并不包括从静态库到可执行文件的所有内容。这不是静态库的工作方式。只有静态库中的单个翻译单元导出与静态库链接的翻译单元中未定义的符号——只有那些翻译单元才会链接到可执行文件中(实际上,它有点复杂,但完整的复杂性是对于这个问题来说无关紧要,只会让事情变得混乱,所以我们将只使用这个简化的描述)。这是静态库的一个定义特征。
main.cpp
对所显示的代码进行非常非常仔细的检查会产生一个深刻的发现,即. 导出的代码中不存在未定义或未解析的符号hash.cpp
。所以hash.cpp
不会链接到可执行文件中。全局对象是在 中定义的hash.cpp
,因此最终的可执行文件不会以程序运行时需要构造的任何全局对象结束。结束。这并不是说静态库的初始化或构造规则发生了变化。这是因为没有什么可以初始化或构造的。