本地静态变量被多次实例化,为什么?

时间:2012-08-15 01:16:33

标签: c++ c++11

我对从此代码获得的结果感到困惑。在一个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;
}

2 个答案:

答案 0 :(得分:8)

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

进一步扩展:

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

修复方法是将Foo::get()的正文移出标题,并将其放入仅在DLL中的源文件中。

答案 1 :(得分:7)

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

然而 ,C ++做定义:DLLs。

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

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

虽然C ++要求带有本地静态变量的内联函数仍然有效,但是不了解DLL的C ++意味着所有内容都符合编译器决定做的事情。

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

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

您必须始终小心跨越DLL边界的内容。通常,不要像这样在DLL边界内嵌内容。