为啥需要显式定义静态变量?

Posted

技术标签:

【中文标题】为啥需要显式定义静态变量?【英文标题】:Why static variable needs to be explicitly defined?为什么需要显式定义静态变量? 【发布时间】:2015-03-24 02:23:03 【问题描述】:

在课堂上:

class foo

public:
    static int bar; //declaration of static data member
;

int foo::bar = 0; //definition of data member

我们必须显式定义静态变量,否则会导致一个

<strong>undefined reference to 'foo::bar'</strong>

我的问题是:

为什么我们必须明确定义静态变量?


请注意,这不是与之前提出的 undefined reference to static variable 问题的重复。这个问题旨在询问显式定义的原因静态变量。

【问题讨论】:

如果这个类在一个用三个独立的翻译单元编译的头文件中,你如何确保不违反ODR,除非只有一个翻译单元在头文件之外有定义? 一些(阅读:恰好一个)编译单元需要包含对foo::bar的引用。 @volerag,它几乎只是基于One Definition Rule。您可以将翻译单元视为一个 .cpp 文件,您将其与每个递归包含的头文件“粘贴”到该 .cpp 文件的顶部一起编译。当您开始组合翻译单元时,标题中定义的内容必然会被多次定义,除非它们被允许(如类定义)或特别注意(如此处讨论的变量)。 这在 C++11 中变得更有趣,它允许在类中初始化,因此在许多情况下,除非使用 odr,否则您不需要外部定义,但弄清楚何时使用 odr 可以是very tricky in many cases。 con't 然后你会看到一些像one from the draft C++14 standard 这样非常古怪的案例。 【参考方案1】:

从一开始,C++ 语言就和 C 一样,是建立在独立翻译的原则之上的。每个翻译单元由适当的编译器独立编译,无需了解其他翻译单元。整个程序只是在稍后的链接阶段才结合在一起。链接阶段是链接器看到整个程序的最早阶段(它被视为由编译器本身准备的目标文件的集合)。

为了支持这种独立翻译的原则,每个具有外部链接的实体都必须定义在一个翻译单元中,并且只能在一个翻译单元中。用户负责在不同翻译单元之间分配此类实体。它被认为是用户意图的一部分,即用户应该决定哪个翻译单元(和目标文件)将包含每个定义。

这同样适用于类的静态成员。类的静态成员是具有外部链接的实体。编译器希望您在某个翻译单元中定义该实体。此功能的全部目的是让您有机会选择该翻译单元。编译器无法为您选择它。这也是你意图的一部分,你必须告诉编译器。

这不再像以前那样重要,因为该语言现在旨在处理(并消除)大量相同的定义(模板、内联函数等),但是一个定义规则仍然根植于独立翻译的原则。

除上述之外,在 C++ 语言中,您定义变量的点将决定其相对于同一翻译单元中定义的其他变量的初始化顺序。这也是用户意图的一部分,即没有你的帮助编译器无法决定的事情。


从 C++17 开始,您可以将静态成员声明为 inline。这消除了单独定义的需要。通过以这种方式声明它们,您可以有效地告诉编译器您不关心该成员的物理定义位置,因此也不关心它的初始化顺序。

【讨论】:

我只是有一个基本问题@AnT,为什么 shouldshould not 在包含包含静态的类定义的标题中定义静态变量变量? @volerag:不,绝对不是。静态成员必须在实现文件中定义(每个成员 - 在您选择的一个且只有一个实现文件中),而不是在头文件中。 @volerag:如果头文件被包含在多个实现文件中(这是头文件的目的),在头文件中定义它会产生多个定义。这是一个 ODR 违规,一个错误。换句话说,它与在头文件中定义一个普通变量是一样的——它会导致 ODR 错误。 @volerag:保护块不会保护您免受 ODR 违规。防护块与它完全无关。保护块可防止您将标头多次包含在相同的翻译单元中。关于将标题包含在 不同的 翻译单元中,它不会改变任何内容。并且 ODR 违规具体来自于包含在不同的翻译单元中。规则是:不要在头文件中定义外部变量或外部非内联函数。没有办法绕过这条规则。 @volerag 请注意答案中指出这不再是关键原因的部分。 inline 函数中允许使用局部静态变量,并且这些变量都将引用同一个对象,无论它们从多少个地方调用,甚至来自不同的源文件。如果 C++ 委员会决定放弃这个要求,他们可以随时放弃。【参考方案2】:

在早期的 C++ 中,允许在类中定义 static 数据成员,这肯定违反了类只是一个蓝图并且不留出内存的想法。现在已经放弃了。

static 成员的定义放在类之外强调内存只为static 数据成员分配一次(在编译时)。该类的每个对象都没有自己的副本。

【讨论】:

【参考方案3】:

static 是一种存储类型,当您声明变量时,您告诉编译器“本周将在某处的数据部分中”并且当您随后使用它时,编译器会发出从 TBD 地址加载值的代码.

在某些情况下,编译器可以驱动一个静态变量实际上是一个编译时常量,并将其替换为例如

static const int meaning = 42;

在从不获取值地址的函数内部。

但是,在处理类成员时,编译器无法猜测应该在哪里创建该值。它可能位于您将链接到的库中,也可能位于 dll 中,或者您可能正在提供一个库,其中值必须由库使用者提供。

通常,当有人问这个问题时,这是因为他们滥用了静态成员。

如果你想要我们一个常数值,例如

static int MaxEntries;
...
int Foo::MaxEntries = 10;

您最好选择以下一项或多项

static const int MaxEntries = 10;
 // or
enum  MaxEntries = 10 ;

在尝试获取变量的地址或形成对变量的引用之前,静态不需要单独的定义,枚举版本永远不会。

【讨论】:

【参考方案4】:

在类中你只是声明变量,即:你告诉编译器有这个名字的东西。 但是,静态变量必须获得一些内存空间才能存在,并且必须在一个翻译单元内。只有当您定义变量时,编译器才会保留此空间。

【讨论】:

【参考方案5】:

结构不是可变的,但它的实例是可变的。因此,我们可以在多个模块中包含相同的结构声明,但我们不能在多个模块中全局定义相同的实例名称

结构的静态变量本质上是一个全局变量。如果我们在结构声明本身中定义它,我们将无法在多个模块中使用结构声明。因为这会导致在多个模块中定义相同的全局实例名称(静态变量),从而导致链接器错误“相同符号的多个定义”

【讨论】:

以上是关于为啥需要显式定义静态变量?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 cpp 文件中定义了非 int const 静态变量?

为啥 C# 公共静态变量不需要实例化?

为啥 C++17 中的全局内联变量和静态内联成员需要守卫?

java中为啥要把main方法定义为一个static方法

静态局部变量

C#中static静态变量的用法