关于c ++:头文件中的变量声明 – 静态与否?

关于c ++:头文件中的变量声明 – 静态与否?

Variable declarations in header files - static or not?

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

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

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

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


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

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

  • extern给出可变外部链接,使其对其他翻译单元可见。通常,这意味着变量必须仅在一个转换单元中定义。

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

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

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

从C规范的草案:

6.2.2 Linkages of identifiers
...
-5- If the declaration of an identifier for a function has no storage-class specifier, its linkage
is determined exactly as if it were declared with the storage-class specifier extern. If
the declaration of an identifier for an object has file scope and no storage-class specifier,
its linkage is external.

从C ++规范的草案:

7.1.1 - Storage class specifiers [dcl.stc]
...
-6- A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.


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

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

编者注:在C ++中,声明中没有staticextern关键字的const对象隐含static


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

test.h:

1
2
static int TEST = 0;
void test();

test1.cpp:

1
2
3
4
5
6
7
#include <iostream>
#include"test.h"

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

测试2.cpp:

1
2
3
4
5
6
#include <iostream>
#include"test.h"

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

运行此命令可以获得此输出:

0x446020
0x446040


C ++中的const变量具有内部链接。因此,使用static无效。

1
const int i = 10;

one.cpp

1
2
3
4
5
6
#include"a.h"

func()
{
   cout << i;
}

two.cpp

1
2
3
4
5
6
#include"a.h"

func1()
{
   cout << i;
}

如果这是一个C程序,你会得到i的"多重定义"错误(由于外部链接)。


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

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

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

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


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


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

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

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

In C, file-scoped variables are extern by default. If you're using C, VAL is static and ANOTHER_VAL is extern.

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

In C++, file-scoped variables are static by default if they are const, and extern by default if they are not. If you're using C++, both VAL and ANOTHER_VAL are static.

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

  • 调试选项
  • 在文件中采取的地址
  • 编译器总是分配存储(复杂的const类型不能简单地内联,因此成为基本类型的特殊情况)

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


const变量默认情况下是C ++中的静态变量,但是extern C.因此,如果使用C ++,则无法理解要使用的构造。

(7.11.6 C ++ 2003,Apexndix C有样本)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

假设这些声明属于全局范围(即不是成员变量),则:

静态意味着"内部联系"。在这种情况下,由于它被声明为const,因此编译器可以对其进行优化/内联。如果省略const,则编译器必须在每个编译单元中分配存储。

通过省略静态,默认情况下链接是extern。同样,你已经被constness保存了 - 编译器可以优化/内联使用。如果删除const,那么在链接时会得到一个多重定义的符号错误。


静态可以防止另一个编译单元对该变量进行外部处理,以便编译器可以在其中使用变量的值"内联"它,而不是为它创建内存存储。

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


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


推荐阅读