7

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

2 回答 2

10

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

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

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

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

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

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

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

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

于 2012-08-15T10:30:58.260 回答
10

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

要进一步扩展:

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

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

于 2012-08-15T01:39:49.077 回答