链接静态库与使用C ++中的目标文件时的不同行为

时间:2016-07-19 23:09:12

标签: c++ static-libraries

我正在处理一些遗留的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

我的问题:它是如何工作的第一种方式而不是第二种方式,是否有办法(例如编译器/链接器选项)使其以第二种方式工作?

提前致谢了。

registry.h:

class Registry {
public:

    static Registry& get_registry();
    bool memberRegistered;

private:
    Registry() {
        memberRegistered = false; 
    }
};

registry.cpp:

#include "registry.h"
Registry& Registry::get_registry() {
    static Registry registry;
    return registry;
}

member.cpp:

#include "registry.h"

int dummy() {
    Registry::get_registry().memberRegistered = true;
    return 0;
}
int x = dummy();

shell.cpp:

#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();
}

3 个答案:

答案 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可执行文件的项目):

  1. 在shell项目中添加对注册表项目的引用;
  2. 在General set下的shell项目的链接器属性中 “链接库依赖关系”和“使用库依赖输入” “是”。
  3. 如果设置了这些选项,则说明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设置以确保将目标文件传递给链接器?有什么想法吗?