C++ 静态变量在不同模块之间共享值

C++ 静态变量在不同模块之间共享值,c++,shared-libraries,static-variables,C++,Shared Libraries,Static Variables,首先是问题上下文:Linux x64,gcc v4.8.5。有一个应用程序加载两个共享库,即module1.so和module2.so。因此,这些模块的代码部分相同。下面是一些代码: //SomeClass.h class SomeClass { public: static unsigned long& s_uObj2() { static unsigned long s_uObj2; return s_uObj2; };

首先是问题上下文:Linux x64,gcc v4.8.5。有一个应用程序加载两个共享库,即module1.so和module2.so。因此,这些模块的代码部分相同。下面是一些代码:

//SomeClass.h
class SomeClass
{
public:
    static unsigned long& s_uObj2()
    {
        static unsigned long s_uObj2;
        return s_uObj2;
    };
    void Initialize();
};

//SomeClass.cpp
void SomeClass::Initialize()
{
     if (0 == s_uObj2())
     {
         //do init
     }
     s_uObj2()++; //acts as a counter
}
这段代码是很久以前写的,它的思想是防止在每个模块中双重初始化某个类。问题在于:该实现在单个应用程序中的不同模块之间以某种方式共享s_uObj2值,这导致只有第一个模块将被初始化

这怎么可能?我想它应该是不同模块之间的隔离地址空间

请不要给我指出静态变量如何工作的一般案例定义。我真正需要的是分析为什么在这种情况下不同的模块共享单个变量的值。这是因为这是一个真实的项目,我无法将其全部重构以使其正常工作

问题在于:此实现以某种方式共享s_uObj2值 跨单个应用程序中的不同模块,这导致只有第一个模块将被初始化

这怎么可能?我想应该是隔离的地址空间 在不同模块之间

<>这是C++标准的要求。该规则基本上说,在整个程序中,具有相同名称的所有全局对象必须解析为单个定义。例如,所有全局变量(包括类静态变量)都必须解析为同一个对象

现在GCC非常努力地在所有共享库中保存ODR。如果构建上述代码并检查其导出的符号,则可以看到它导出了SomeClass::s_uObj2:

这意味着在启动时,动态链接器将把SomeClass::s_uObj2::s_uObj2的所有重复副本解析为一个对象,该对象是碰巧加载的第一个共享库中的副本

如果您真的愿意放弃ODR,克服此问题的通常方法是避免从库中导出s_uObj2,即限制其可见性

有很多方法可以做到这一点,我只列举几个:

编译时隐藏-fvisibility内联线

将_属性_可见性隐藏到s_uObj2的定义

把你的班级声明放进去

pragma GCC可见性隐藏

布拉格语


第一个是讨厌的,因为它将有效地禁用所有代码的ODR,而不仅仅是上面的代码片段。后两种更细粒度。

相关@FrançoisAndrieux我在这篇文章中没有看到任何有用的东西——它说,在所有情况下,静态全局变量或函数从模块dll/so或可执行文件外部都是不可见的。在我的例子中,我在不同的模块中有不同的全局变量。在你的例子中,你没有静态全局变量,你有一个静态局部变量,这在很多方面与非静态全局变量类似。阅读关于Linux中的外部全局函数的部分。@FrançoisAndrieux,当然谢谢,但实际上,通过指向其他一些一般情况,您并没有提供任何帮助。您是说两个库中都存在某个类吗?这个问题有点不清楚。动态库不会加载到单独的地址空间,而是加载到应用程序地址空间的不同部分。看来gcc/Linux在这方面比Visual Studio/Windows更难工作。几年前,我遇到了多个单例的问题,因为不同的DLL有自己的本地静态变量副本。就是这样!谢谢你能解释一下为什么这只发生在Linux中吗?我已经在WindowsVisualStudio2010和Android NDK中测试了这段代码-没有这样的问题。实际上,GCC的正确参数是-fvisibility=hidden@AlekDepler-fvisibility=hidden比我不推荐的-fvisibility inlines hidden更危险。我将隐藏库中的所有符号,从而使其无用,除非您随后特别注释导出的函数。使用默认隐藏可见性重建软件是一项巨大的更改,不应掉以轻心。@MarkRansom确实如此,但这是有成本的-不允许GCC像Visual Studio那样优化共享库中的函数。
$ g++ tmp.cpp -shared -fPIC && objdump -T a.out | c++filt
...
0000000000200970  w   DO .bss   0000000000000008  Base        SomeClass::s_uObj2()::s_uObj2