【问题标题】:cpp file with a single global variable is ignored具有单个全局变量的 cpp 文件被忽略
【发布时间】:2014-05-06 00:31:09
【问题描述】:

我正在尝试初始化全局地图

std::map<long, std::string> Global_ID_Mapper;

具有许多“init”类,例如:

struct AGlobalMapperInitializer
{
    AGlobalMapperInitializer() 
    {
        Global_ID_Mapper.insert( std::make_pair(1, "Value1") );
        Global_ID_Mapper.insert( std::make_pair(2, "Value2") );
    }
};

我想在应用程序启动期间自动填充地图。所以在我的一个 cpp 文件中,我只定义了那个“init”类的全局变量。

// AGlobalMapperInitializer.cpp

AGlobalMapperInitializer AGlobalMapperInitializer_Value;

Mapper 填充是 AGlobalMapperInitializer_Value 创建的副作用。

问题在于,如果 cpp 除了这个全局变量之外不包含任何内容,那么链接器显然会忽略 cpp。当我将一些有用的其他代码放入 cpp(或在某些非空 cpp 中定义全局初始化程序)时,将调用构造函数并填充全局映射器。但是如果 cpp 只包含没有在其他文件中引用的全局,则 cpp 被编译,obj 文件包含变量,但链接器在链接过程中没有提及它,它在 exe 中被遗漏。

我怎样才能坚持将cpp链接到exe? 是否有一些编译指示或虚拟代码可以放入 cpp 以使其不被忽略? 我使用 Visual Studio 2012。

【问题讨论】:

  • 一种常见的替代方法是在第一次使用时初始化全局/单例。
  • 您可以 (Dll) 导出整个类 AGlobalMapperInitializer 并使全局成为该类的静态成员,或者在标头中将全局声明为 extern (Dll) 导出到类外部。 (否则全局是翻译单元中的局部并优化出来)
  • 链接器会丢弃所有未引用的对象。除了声明不应导出的导出之外,您还可以在任何函数中引用它。请注意编译器无法优化此引用,例如 main() { printf("", &amp;AGlobalMapperInitializer_Value)); }
  • @harper 你的方法有效,我发现了。问题是我不想污染不相关的代码。
  • @MooseBoys 这不是我的情况。单例/全局 Global_ID_Mapper 不知道他的初始化程序(在不同的项目中有很多)。

标签: c++ visual-studio-2012 linker


【解决方案1】:

感谢帮助找到解决方案的 Angew 和 harper。

有两种可能的解决方案:

  1. 便携式(但性能不佳)。

    初始化器实例可以不在专用的 cpp 文件中定义,而是在 h 中,使用单词static

    // AGlobalMapperInitializer.h
    // It wasn't mentioned before that this h file is included in many cpp files
    struct AGlobalMapperInitializer
    {
        AGlobalMapperInitializer() 
        {
            if( !Global_ID_Mapper.insert( std::make_pair(1, "Value1") ).second ) 
                return;
            Global_ID_Mapper.insert( std::make_pair(2, "Value2") );
        }
    };
    
    static AGlobalMapperInitializer AGlobalMapperInitializer_Value;
    

    由于变量是static,所以AGlobalMapperInitializer_Value是在所有包含h文件的cpp文件中单独创建的。这些变量中的每一个都试图向全局映射器添加值。当然,只有其中一个成功了。通过检查第一次插入的结果部分解决了性能问题。如果第一个值已插入 - 无需尝试其他值。

    注意:假设所有这些实例都将在同一个线程中创建,否则 Global Mapper 应该实现插入调用的同步。

  2. 特定于编译器的解决方案 (Visual Studio)。

    可以通过添加#pragma comment (linker, "/include:&lt;decorated name&gt;") 来强制链接器保持类。修饰名称可以是类构造函数或任何其他函数。问题是指定修饰名称硬代码既不好也不方便。装饰方法可以随着编译器升级而改变。所以,这里可以使用__FUNCDNAME__

    struct AGlobalMapperInitializer
    {
        AGlobalMapperInitializer() 
        {
            // Make sure the class will not be threw away by linker
            #pragma comment (linker, "/include:"__FUNCDNAME__) 
    
            Global_ID_Mapper.insert( std::make_pair(1, "Value1") );
            Global_ID_Mapper.insert( std::make_pair(2, "Value2") );
        }
    };
    

    幸运的是,#pragma 可以在类构造函数中指定。

【讨论】:

    【解决方案2】:

    链接器不包含该变量,它是未引用时的初始化程序。您可以在代码中创建对此变量的引用:

    int main()
    {
        printf("", &AGlobalMapperInitializer_Value));
    }
    

    如果您想避免源代码的这种污染,您可以使用链接器的/INCLUDE 参数产生相同的效果。当您尝试上述 hack 时,您必须添加可以从 .map 文件中获取的修饰名称。

    VS2010 在项目属性中提供此选项为Force Symbol Reference:配置属性-> 链接器-> 输入。我希望 VS2012 也一样。

    【讨论】:

    • 谢谢。我错过了 /INCLUDE 选项应该与装饰名称一起使用(尽管很明显应该是这样)。所以现在我在我的 h 文件中添加了#pragma comment(linker, "/include:?AGlobalMapperInitializer_Value@@3VAGlobalMapperInitializer@@A") 并得到了我想要的。然而,如果添加到被忽略的 cpp,这个 #pragma 将不起作用。
    • 但是使用#pragma 会污染源代码。我会考虑将其隐藏在项目属性中。 ;-)
    • 问题是我有一些静态库最近被编译成几个 exe。更改 lib 项目的 Force Symbol References 无济于事。无论如何,链接器都会抛出该符号,为了保留它,我必须更改最终链接项目的Force Symbol References。但我想将所有库的详细信息保留在库的级别。此外,我无法访问链接lib的一些项目,我将进行更改。我发现工作的唯一方法 - #pragma。而且我还可以避免使用__FUNCDNAME__ 指定修饰名称硬代码。
    【解决方案3】:

    如果没有引用与 x 相同的文件(实际上是翻译单元)中的函数或变量,则 C++ 不需要初始化全局变量 x

    见 C++11 [basic.start.init]§4:

    是否在main的第一条语句之前完成对具有静态存储时长的非局部变量的动态初始化是实现定义的。如果初始化被推迟到main 的第一条语句之后的某个时间点,它应该在与要初始化的变量在同一翻译单元中定义的任何函数或变量的第一次 odr-use (3.2) 之前发生。

    所以要强制变量初始化,你必须把它放到你实际使用的其他内容的文件中,或者直接在某个地方使用变量。

    【讨论】:

    • 很好,它完全解释了我的问题。但是你对这个问题有什么想法吗:Is there some pragma or dummy code to put into the cpp to get it non-ignored?
    • @user940014 #pragmas 依赖于编译器,因此请查阅编译器的文档。至于“虚拟代码”,我的答案列出了选项:使用变量,或者将它们与您使用的东西放在一起(这也包括“把其他东西放在那里并使用它”)。
    • @Andew 我是否理解正确:我无法避免更改其他一些 cpp?我的意思是,如果我什至在第一个 cpp 中添加了一些东西(虚拟代码),我仍然必须将一些代码添加到另一个将使用该虚拟代码的 cpp 中。
    • @Kunis 是的。除非您的编译器提供 #pragma 或其他特定于编译器的东西,否则唯一可移植的 C++ 方法是在程序的其他位置使用该文件中的某些内容。
    猜你喜欢
    • 1970-01-01
    • 2019-11-14
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-26
    相关资源
    最近更新 更多