C++中类静态变量的生命周期是多久?

14

如果我有一个名为Test的类 ::

class Test
{
    static std::vector<int> staticVector;
};

静态变量 staticVector 何时被构造并何时被析构?

是在 Test 类的第一个对象实例化时,还是像常规静态变量一样?

只是为了澄清,我是在阅读《程序设计语言的概念》(Sebesta Ch-5.4.3.1)后才有这个问题。书中提到:

请注意,在 C++、Java 和 C# 中,当 static 修饰符出现在类定义中变量的声明中时,它与变量的生命周期无关。在这种情况下,它表示变量是类变量而不是实例变量。保留字的多次使用可能会令人困惑,尤其是对于那些正在学习该语言的人。

你明白吗?:(

5个回答

17

我想写一些关于初始化的文本,以便以后可以链接到它。


首先是可能性列表。

  • 命名空间 静态
  • 静态
  • 局部变量 静态

命名空间静态

  • 有两种初始化方法。静态(意在编译时发生)和动态(意在运行时发生)初始化。
  • 静态初始化会在任何动态初始化之前发生,无论翻译单元的关系如何。
  • 动态初始化在翻译单元中有顺序,而静态初始化没有特定的顺序。同一翻译单元的命名空间范围对象以它们的定义出现顺序进行动态初始化。
  • 使用常量表达式初始化的POD类型对象是静态初始化的。它们的值可以被任何对象的动态初始化所依赖,无论翻译单元的关系如何。
  • 如果初始化引发异常,则调用std::terminate

例子:

以下程序将打印A(1)A(2)

struct A { 
  A(int n) { std::printf(" A(%d) ", n); } 
};

A a(1);
A b(2);

以下代码基于同一类打印:A(2)A(1)
extern A a;
A b(2);
A a(1);

假设存在一个翻译单元,其中msg被定义为以下内容

char const *msg = "abc";

接下来会打印abc。请注意,p 接收动态初始化。但是因为静态初始化(char const* 是 POD 类型,"abc" 是地址常量表达式)在此之前发生,所以这是可以的,并且保证了 msg 被正确初始化。
extern const char *msg;
struct P { P() { std::printf("%s", msg); } };
P p;
  • 对象的动态初始化不必要在main函数之前发生。但是,它必须在其翻译单元中的对象或函数第一次使用之前发生初始化。这对于动态可加载库非常重要。

类静态

- 表现得像命名空间静态变量。 - 是否允许编译器在函数或对象第一次使用后(在main函数之后)初始化类静态变量存在一个错误报告。标准中的措辞目前仅允许对命名空间范围内的对象进行此操作,但似乎也意图允许对类范围内的对象进行此操作。请阅读[命名空间范围内的对象](http://groups.google.com/group/comp.std.c++/browse_thread/thread/28cfef85456512c8)。 - 对于作为模板成员的类静态变量,规则是只有在使用它们时才会初始化。不使用它们将不会导致初始化。请注意,在任何情况下,初始化将按上述说明进行。初始化不会因为它是模板的成员而被延迟。

局部静态

对于本地静态变量,会发生特殊规则。 使用常量表达式初始化的POD类型对象在它们所定义的块被进入之前就已经被初始化。 其他本地静态对象在第一次控制通过它们的定义时被初始化。当抛出异常时,初始化不被认为是完成的。初始化将在下一次尝试。

示例:以下程序打印0 1

struct C { 
  C(int n) { 
    if(n == 0)
      throw n;
    this->n = n;
  }
  int n;
};

int f(int n) {
  static C c(n);
  return c.n;
}

int main() {
  try { 
    f(0); 
  } catch(int n) { 
    std::cout << n << " "; 
  }
  f(1); // initializes successfully
  std::cout << f(2);  
}

在所有上述情况中,对于某些不需要静态初始化的对象,在某些有限的情况下,编译器可以静态初始化它,而不是动态初始化它。这是一个棘手的问题,请参见this answer以获取更详细的示例。
另请注意,销毁顺序是对象构造完成的确切顺序。这是C++中常见的情况,在各种情况下都会发生,包括在销毁临时对象时。

15

就像普通的静态(全局)变量一样。


2
除此之外,需要指出的唯一一点是静态/全局变量构造的顺序是未定义的。 - Skizz
我还要补充一点,这就是任何命名空间中变量的创建方式。全局变量和非全局变量(std::cout 就是一个例子)。 - Johannes Schaub - litb
3
在特定的编译单元中,顺序是按照声明的顺序定义的。在多个编译单元之间,顺序是未定义的(或不确定的)。但问题是关于销毁方面的。 - Martin York
4
静态/全局变量的销毁顺序与创建顺序相反。 - Martin York
6
有一个细节需要注意,但这个细节很重要:变量的顺序取决于它们的定义顺序而不是声明顺序。在以下代码中,尽管a先被声明,但它实际上是在b之后创建的:**extern A a; B b(a); A a;** - 这段代码可能会产生令人惊讶的结果。 - Johannes Schaub - litb

5
它与全局变量同时构建,随着全局变量一起被销毁。

首次使用时不会创建任何静态函数变量。如果它们没有被使用,则永远不会被创建。此外,因为它们是在第一次使用时创建的,它们的顺序取决于调用函数/方法的顺序。 - Martin York
1
是的,但他并没有问关于静态函数变量的问题...但是没错...你是正确的。 - Goz

3
简单来说: 静态成员变量在全局变量被构建时就被构建了。全局变量的构建顺序没有定义,但是它会在进入主函数之前发生。
销毁发生在全局变量被销毁时。
全局变量按构建顺序的反向顺序被销毁;在退出主函数后。
敬礼, Ovanes
附言:建议查看C++标准,它解释(定义)了何时以及如何构造或销毁全局或静态成员变量。
另外,你的代码只声明了一个静态成员变量,但没有初始化它。要初始化它,必须在编译单元中写入以下代码之一:
std::vector Test::staticVector; 或 std::vector Test::staticVector=std::vector(/* 构造函数参数 */);

1

如果您正在使用VC++,则有一些特定的信息:

  1. 静态类变量的构造与其他静态/全局变量同时发生。
  2. 在Windows中,CRT启动函数负责此构造。 这是大多数编译程序的实际入口(它是调用Main/Winmain函数的函数)。 此外,它负责初始化整个C运行时支持(例如需要它来使用malloc)。
  3. 构建顺序未定义,但是使用Microsoft VC编译器时基本类型的构建顺序将是正确的,例如编写以下内容是合法和安全的

statics.h: ... MyClass声明... static const int a; static int b; static int ar[]; } statics.cpp:

const int MyClass::a = 2;
int MyClass::b = a+3;
int MyClass::ar[a] = {1,2}

1
这个问题涉及到C++,而不是Visual C++或Windows。最新版本的MSVC相当符合标准,但你不能使用单一编译器作为参考或证据。标准应该是你所依赖的明确来源。全局或静态成员变量的生命周期和它们的初始化在标准中得到了很好的覆盖。为什么要对非成员变量使用static限定符?这将使它们具有内部链接!我认为你会在其他编译单元(如statics.cpp)中遇到链接错误。 - ovanes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接