我正在处理一些遗留的C ++代码,这些代码的表现方式我不明白。我使用的是Microsoft编译器,但我也尝试使用g ++(在linux上) - 同样的行为。
我有4个文件,如下所示。从本质上讲,它是一个能够跟踪成员列表的注册表。如果我编译所有文件并将目标文件链接到一个程序,它会显示正确的行为:registry.memberRegistered为true:
>cl shell.cpp registry.cpp member.cpp
>shell.exe
1
所以不知何故,member.cpp中的代码被执行(我真的不明白,但是没问题)。 但是,我想要的是从registry.cpp和member.cpp构建一个静态库,并将其链接到shell.cpp构建的可执行文件。但是当我这样做时,member.cpp中的代码执行而不是并且registry.memberRegistered为false:
>cl registry.cpp member.cpp /c
>lib registry.obj member.obj -OUT:registry.lib
>cl shell.cpp registry.lib
>shell.exe
0
我的问题:它是如何工作的第一种方式而不是第二种方式,是否有办法(例如编译器/链接器选项)使其以第二种方式工作?
提前致谢了。
class Registry {
public:
static Registry& get_registry();
bool memberRegistered;
private:
Registry() {
memberRegistered = false;
}
};
#include "registry.h"
Registry& Registry::get_registry() {
static Registry registry;
return registry;
}
#include "registry.h"
int dummy() {
Registry::get_registry().memberRegistered = true;
return 0;
}
int x = dummy();
#include <iostream>
#include "registry.h"
class shell {
public:
shell() {};
void init() {
std::cout << Registry::get_registry().memberRegistered;
};
};
void main() {
shell *cf = new shell;
cf->init();
}
答案 0 :(得分:4)
你被众所周知的static initialization order fiasco所击中。
基础是未指定跨翻译单元的静态对象的初始化顺序。请参阅this
“shell.cpp”中的Registry::get_registry().memberRegistered;
调用可能发生在“member.cpp中int x = dummy();
的调用之前“ 撞击>
修改强>
好吧,x
不是ODR-used。因此,允许编译器在输入int x = dummy();
之前或之后,甚至根本不评估main()
。
来自CppReference (强调我的)
的引用是否是动态初始化的实现定义 发生在主函数的第一个语句之前(对于静态) 或线程的初始函数(对于线程本地),或延迟 发生之后。
如果初始化延迟发生在第一个语句之后 主/线程函数,它发生在任何第一次使用odr之前 变量与静态/线程存储持续时间定义相同 翻译单元作为要初始化的变量。 如果没有来自给定翻译单元的变量或函数,则该翻译单元中定义的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。 ..
让您的程序正常工作的唯一方法是确保x
使用ODR
<强> shell.cpp 强>
#include <iostream>
#include "registry.h"
class shell {
public:
shell() {};
void init() {
std::cout << Registry::get_registry().memberRegistered;
};
};
extern int x; //or extern int dummy();
int main() {
shell *cf = new shell;
cf->init();
int k = x; //or dummy();
}
^现在,您的程序应该按预期工作。 : - )
答案 1 :(得分:0)
这是链接器处理库的方式的结果:它们选择定义符号的对象,这些符号是目前为止处理的其他对象未定义的。这有助于保持较小的可执行文件大小,但是当静态初始化具有副作用时,它会导致您发现的可疑行为:member.obj / member.o根本没有链接到程序中,尽管它的存在会做点什么。
使用g ++,您可以使用:
g++ shell.cpp -Wl,-whole-archive registry.a -Wl,-no-whole-archive -o shell
强制链接器将所有库放入程序中。 MSVC可能有类似的选项。
答案 2 :(得分:0)
非常感谢所有的回复。非常有帮助。
因此,提出的WhiZTiM(使用x ODR)和aschepler(强制链接器包含整个库)的解决方案都适用于我。后者有我的偏好,因为它不需要对代码进行任何更改。但是,似乎没有--whole-archive的MSVC等价物。 在Visual Studio中,我设法解决了以下问题(我有一个用于注册表静态库的项目,以及一个用于shell可执行文件的项目):
如果设置了这些选项,则说明registry.memberRegistered已正确初始化。但是,在研究了编译器/链接器命令之后,我得出结论,设置这些选项会导致VS简单地将registry.obj和member.obj文件传递给链接器,即:
>cl /c member.cpp registry.cpp shell.cpp
>lib /OUT:registry.lib member.obj registry.obj
>link /OUT:shell.exe "registry.lib" shell.obj member.obj registry.obj
>shell.exe
1
在我看来,这基本上是我原始问题中的第一种方法。如果在链接器命令中省略了registry.lib,它也能正常工作。 无论如何,现在对我来说已经足够了。
我正在使用CMake所以现在我需要弄清楚如何调整CMake设置以确保将目标文件传递给链接器?有什么想法吗?