93

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

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

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

VAL如果标头包含在多个源文件中,静态是否意味着只创建一个副本?

4

12 回答 12

112

文件范围变量上的staticandextern标记确定它们是否可在其他翻译单元(即其他.c.cpp文件)中访问。

  • static提供变量内部链接,将其隐藏在其他翻译单元中。但是,可以在多个翻译单元中定义具有内部链接的变量。

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

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

  • 在 C 中,文件范围的变量extern默认为(外部链接)。如果您使用的是 C,VAL则 isstaticANOTHER_VALis extern

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

来自C 规范的草案:

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

来自C++ 规范的草案:

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

于 2008-09-18T15:37:28.627 回答
109

static意味着将为VAL包含它的每个源文件创建一个副本。但这也意味着多个包含不会导致多个定义VAL在链接时发生冲突。在 C 中,如果没有 ,static您将需要确保仅定义一个源文件VAL而其他源文件声明它extern。通常可以通过在源文件中定义它(可能使用初始化程序)并将extern声明放在头文件中来做到这一点。

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


编者注:在 C++ 中,声明中conststatic没有 orextern关键字的对象是隐式static.

于 2008-09-18T13:43:40.133 回答
49

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

测试.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

于 2008-09-18T13:50:21.817 回答
6

constC++ 中的变量具有内部链接。所以,使用static没有任何效果。

const int i = 10;

一.cpp

#include "a.h"

func()
{
   cout << i;
}

二.cpp

#include "a.h"

func1()
{
   cout << i;
}

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

于 2010-04-27T02:18:06.607 回答
5

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

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

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

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

于 2008-09-18T13:46:49.127 回答
2

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

于 2008-09-18T13:56:22.520 回答
2

要回答这个问题,“静态是否意味着只创建一个 VAL 的副本,以防标头包含在多个源文件中?”...

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

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

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

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

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

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

  • 调试选项
  • 文件中的地址
  • 编译器总是分配存储空间(复杂的 const 类型不容易内联,因此成为基本类型的特例)
于 2008-09-18T20:47:42.877 回答
1

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

static表示“内部链接”。在这种情况下,由于它被声明为const,因此编译器可以对其进行优化/内联。如果省略const,则编译器必须在每个编译单元中分配存储空间。

通过省略静态链接默认为外部链接。同样,您已经被const性所拯救 - 编译器可以优化/内联使用。如果您删除const,那么您将在链接时收到多重定义的符号错误。

于 2008-09-18T13:49:53.830 回答
1

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

于 2013-09-29T10:34:49.687 回答
1

const变量在 C++ 中默认是静态的,但在 C++ 中是外部的。所以如果你使用 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
于 2015-01-23T09:35:56.273 回答
0

静态阻止另一个编译单元将该变量外部化,以便编译器可以在使用它的地方“内联”变量的值,而不为它创建内存存储。

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

于 2008-09-18T13:35:38.623 回答
-2

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

于 2008-09-18T13:40:11.280 回答