【问题标题】:Local static variable is instantiated multiple times, why?局部静态变量被实例化多次,为什么?
【发布时间】:2012-08-15 01:16:33
【问题描述】:

我对从这段代码中得到的结果感到困惑。在一个 dll 中,当初始化静态变量时,计数器会递增。然后当 main 执行时,我读取了这个计数器,但我得到的是 0 而不是 1。有人可以向我解释一下吗?

在我的动态库项目中:

// Header file
class Foo {
   int i_ = 0;

   Foo(const Foo&) = delete;
   Foo& operator= (Foo) = delete;

   Foo()
   {
   }

public:
   void inc()
   {
      ++i_;
   }

   int geti()
   {
      return i_;
   }

   static Foo& get()
   {
      static Foo instance_;
      return instance_;
   }

   Foo( Foo&&) = default;
   Foo& operator= (Foo&&) = default;
};

int initialize()
{
   Foo::get().inc();
   return 10;
}

class Bar
{
   static int b_;

};

// cpp file
#include "ClassLocalStatic.h"


int Bar::b_ = initialize();

在我的应用项目中

// main.cpp
#include <iostream>

#include "ClassLocalstatic.h"

int main(int argc, const char * argv[])
{
   std::cout << Foo::get().geti();
   return 0;
}

【问题讨论】:

  • 尝试查找静态订单初始化惨败
  • 我刚试过这个,它给了我 1。也许你不应该依赖静态初始化顺序......
  • @Antimony 我认为情况并非如此。因为在这种情况下定义的顺序......当 Bar::b_ 被初始化时,它将实例化 Foo 并增加计数器。这将在 main 之前发生。它确实如此,但问题是在主 Foo::get() 中实际上创建了一个新的 Foo,与在 initialize() 中创建的不同。那是我不明白的。另请注意,如果所有这些代码都是同一个库,则输出为 1。
  • @Henry Hu 我不依赖于初始化顺序。它是在这种情况下定义的。您确定您在另一个项目中使用 lib 和 main 中的代码进行了测试吗?我可能怀疑 Xcode 中存在错误,但我真的很怀疑......
  • @monamimani 我将第一个 .h 和 .cpp 编译成 .so,然后将第二个编译成与库链接的可执行文件。

标签: c++ c++11


【解决方案1】:

可执行文件和 DLL 都将获得自己的 Foo::get() 副本,每个副本都有自己的静态变量副本。因为它们位于单独的链接器输出中,所以链接器无法像往常一样合并它们。

进一步扩展:

C++ 规范允许在多个翻译单元中定义内联函数,只要它们都具有相同的主体;将函数放在头文件中是完全可以的,因为它确保每个副本都是相同的。见https://stackoverflow.com/a/4193698/5987。如果内联函数中有静态变量,编译器和链接器需要协同工作,以确保它们之间只使用一个副本。我不确定确切的机制,但没关系,标准需要它。不幸的是,链接器在生成输出可执行文件或 DLL 后停止访问,并且无法判断该函数在两个位置都存在。

解决方法是将Foo::get() 的主体移出标头并将其放入仅在 DLL 中的源文件中。

【讨论】:

  • 但这没有意义。如果您在一个库中有一个单例并且您从同一个库更改了它的状态,那么另一个库将看不到这些新状态。
  • @monamimani:问题是你的函数是内联的。所以每个可执行文件都有自己单独的副本。您需要将函数定义从标题中取出。
  • 如果这个答案是正确的,那么为什么链接器/加载器不抱怨冲突?
  • @NicolBolas 真的吗?假设我使用了一个将其实例声明为局部静态变量的单例,这意味着每次调用 getInstance() 都会获得该单例的新副本。我怀疑是这种情况。
  • @Walter,明确允许内联函数有多个定义,只要它们相同。查看我的更新。
【解决方案2】:

C++ 的规则规定内联函数定义将与静态局部变量一起正常工作。也就是说,如果你内联函数定义,任何局部静态变量都将引用同一个变量。

但是,C++ 没有定义一件事:DLL。

C++ 规范完全不知道 DLL;它不知道如何处理它们。 C++ 是根据静态链接定义的,而不是动态的。

因此,这意味着规范在处理 DLL 边界时不再适用。这就是您的问题所在。

虽然 C++ 要求具有局部静态变量的内联函数仍然有效,但 C++ 不了解 DLL 意味着一切都取决于编译器 决定做什么。

对于跨 DLL 边界拆分的内联函数不让局部静态变量按预期工作是完全合法的编译器行为。这是一个异常情况,我严重怀疑任何编译器开发人员都花时间为这种可能性进行编码。

对于编译器来说,最合理的做法就是在 DLL 标头中声明一个 extern 全局变量时所做的事情:每个 DLL 和可执行文件都有一个单独的。这就是为什么你需要特殊的语法来说明一个定义应该由这个可执行文件/DLL (__declspec(dllexport)) 定义,什么将来自其他可执行文件/DLL (__declspec(dllimport))。

您必须始终小心放置跨 DLL 边界的内容。一般来说,不要像这样跨 DLL 边界内联内容。

【讨论】:

  • 这种行为真的合规还是仅仅合理? C++ 标准不涉及 DLL,但这并不意味着 DLL 是空白支票。如果标准说静态局部变量适用于内联函数,则必须遵守兼容的实现。
猜你喜欢
  • 2016-09-15
  • 1970-01-01
  • 2013-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-23
相关资源
最近更新 更多