当重构一些 #defines 我在 C++ 头文件中遇到类似于以下内容的声明:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

问题是,静电会产生什么差异(如果有的话)?请注意,由于经典的原因,不可能多次包含标头 #ifndef HEADER #define HEADER #endif 技巧(如果这很重要的话)。

static 是否意味着只有一份 VAL 创建后,万一标头被多个源文件包含?

有帮助吗?

解决方案

static 意味着将会有一份副本 VAL 为包含它的每个源文件创建。但这也意味着多重包含不会导致多重定义 VAL 这会在链接时发生冲突。在 C 中,如果没有 static 您需要确保只定义了一个源文件 VAL 而其他源文件声明了它 extern. 。通常,人们会通过在源文件中定义它(可能使用初始值设定项)并将其放入 extern 头文件中的声明。

static 全局级别的变量仅在其自己的源文件中可见,无论它们是通过包含到达的还是在主文件中。


编者注: 在 C++ 中, const 既不具有 static 也不 extern 其声明中的关键字是隐式的 static.

其他提示

staticextern 文件范围变量上的标签决定它们是否可以在其他翻译单元中访问(即其他 .c 或者 .cpp 文件)。

  • static 给出变量的内部链接,将其从其他翻译单元中隐藏。然而,具有内部链接的变量可以在多个翻译单元中定义。

  • extern 给出变量的外部链接,使其对其他翻译单元可见。通常,这意味着该变量只能在一个翻译单元中定义。

默认值(当您未指定时 static 或者 extern)是 C 和 C++ 不同的领域之一。

  • 在 C 语言中,文件范围的变量是 extern (外部链接)默认情况下。如果你使用C语言, VALstaticANOTHER_VALextern.

  • 在 C++ 中,文件范围的变量是 static (内部链接)默认情况下,如果它们是 const, , 和 extern 默认情况下,如果他们不是。如果您使用 C++,则两者 VALANOTHER_VALstatic.

从草案 C规格:

6.2.2标识符的链接...-5-如果函数标识符的声明没有存储类规范符,则其链接的确定恰好确定,就像用存储级规范符外部声明其链接一样。如果对象的标识符声明具有文件范围,并且没有存储类规范符,则其链接是外部的。

从草案 C++规范:

7.1.1-存储类规范[DCL.STC] ...-6- 在没有存储类说明符的命名空间范围中声明的名称具有外部链接,除非它由于先前的声明而具有内部链接,并且没有声明为 const。声明为 const 且未显式声明为 extern 的对象具有内部链接。

静态意味着您每个文件都会获得一份副本,但与其他人所说的不同的是,这样做是完全合法的。您可以使用一个小代码示例轻松测试它:

测试.h:

static int TEST = 0;
void test();

测试1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

测试2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

运行此命令会得到以下输出:

0x446020
0x446040

const C++ 中的变量具有内部链接。所以,使用 static 没有影响。

const int i = 10;

一个.cpp

#include "a.h"

func()
{
   cout << i;
}

两个.cpp

#include "a.h"

func1()
{
   cout << i;
}

如果这是一个 C 程序,您将收到“多重定义”错误 i (由于外部链接)。

此级别代码的静态声明意味着该变量仅在当前编译单元中可见。这意味着只有该模块内的代码才能看到该变量。

如果您有一个头文件声明了一个 static 变量,并且该头文件包含在多个 C/CPP 文件中,那么该变量对于这些模块来说将是“本地”的。对于包含标头的 N 个位置,将有该变量的 N 个副本。他们彼此之间根本没有关系。这些源文件中的任何代码都只会引用该模块中声明的变量。

在这种特殊情况下,“static”关键字似乎没有提供任何好处。我可能会错过一些东西,但这似乎并不重要——我以前从未见过这样的事情。

至于内联,在这种情况下,变量可能是内联的,但这只是因为它被声明为 const。编译器 可能 更有可能内联模块静态变量,但这取决于情况和正在编译的代码。不能保证编译器会内联“静态”。

C书(免费在线)有一章是关于链接的,它更详细地解释了“静态”的含义(尽管其他评论中已经给出了正确答案):http://publications.gbdirect.co.uk/c_book/chapter4/linkage.html

为了回答这个问题,“静态是否意味着仅创建一份 VAL 副本,以防标头被多个源文件包含?”...

. 。VAL 将始终在包含标头的每个文件中单独定义。

在这种情况下,C 和 C++ 的标准确实会造成差异。

在 C 中,文件范围的变量默认是 extern 的。如果您使用 C,则 VAL 是静态的,ANOTHER_VAL 是外部的。

请注意,如果标头包含在不同的文件中(相同的全局名称定义了两次),现代链接器可能会抱怨 ANOTHER_VAL,并且如果 ANOTHER_VAL 在另一个文件中初始化为不同的值,则肯定会抱怨

在 C++ 中,如果文件范围变量是 const,则默认为 static;如果不是 const,则默认为 extern。如果您使用 C++,则 VAL 和 ANOTHER_VAL 都是静态的。

您还需要考虑两个变量都被指定为 const 的事实。理想情况下,编译器总是选择内联这些变量并且不包含它们的任何存储。分配存储的原因有很多。我能想到的...

  • 调试选项
  • 文件中获取的地址
  • 编译器总是分配存储(复杂的 const 类型不容易内联,因此成为基本类型的特例)

您不能在没有定义静态变量的情况下声明它(这是因为存储类修饰符 static 和 extern 是互斥的)。可以在头文件中定义静态变量,但这会导致包含该头文件的每个源文件都有自己的变量私有副本,这可能不是预期的目的。

假设这些声明处于全局范围内(即不是成员变量),那么:

静止的 意思是“内部联系”。在这种情况下,由于它被声明为 常量 这可以由编译器优化/内联。如果您省略 常量 那么编译器必须在每个编译单元中分配存储空间。

通过省略 静止的 联系是 外部的 默认情况下。再一次,你被拯救了 常量ness - 编译器可以优化/内联使用。如果您删除 常量 那么你会得到一个 多重定义的符号 链接时出错。

常量 变量在 C++ 中默认是 static,但在 C 中是 extern。因此,如果您使用 C++,则没有意义使用什么构造。

(7.11.6 C++ 2003,Apexndix C 有示例)

比较 C 和 C++ 程序的编译/链接源的示例:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609

静态可防止另一个编译单元外部使用该变量,以便编译器可以仅在使用变量的位置“内联”该变量的值,而不会为其创建内存存储。

在第二个示例中,编译器不能假设其他某些源文件不会 extern 它,因此它实际上必须将该值存储在内存中的某个位置。

静态会阻止编译器添加多个实例。对于 #ifndef 保护,这一点变得不那么重要,但假设标头包含在两个单独的库中,并且应用程序已链接,则将包含两个实例。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top