头文件中的变量声明 - 静态与否?

时间:2008-09-18 13:32:06

标签: c++ c static

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

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

问题是,静态会产生什么区别?请注意,由于经典的#ifndef HEADER #define HEADER #endif技巧(如果重要),无法多次包含标题。

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

12 个答案:

答案 0 :(得分:106)

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

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

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

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

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

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

来自C specification的草稿:

  

6.2.2标识符的链接   ...   -5-如果函数的标识符声明没有存储类说明符,则其链接   确定与存储类说明符extern声明的完全相同。如果   对象标识符的声明具有文件范围,没有存储类说明符,   它的联系是外部的。

来自C++ specification的草稿:

  

7.1.1 - 存储类说明符[dcl.stc]   ...   -6-在没有存储类说明符的命名空间作用域中声明的名称具有外部链接,除非由于先前的声明而具有内部链接,并且未将其声明为const。声明为const且未显式声明为extern的对象具有内部链接。

答案 1 :(得分:100)

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

全局级别的

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


编者注:在C ++中,声明中staticconst关键字的static对象都隐含extern

答案 2 :(得分:45)

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

test.h:

static int TEST = 0;
void test();

test1.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

答案 3 :(得分:6)

C ++中的

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

<强> A.H

const int i = 10;

<强> one.cpp

#include "a.h"

func()
{
   cout << i;
}

<强> two.cpp

#include "a.h"

func1()
{
   cout << i;
}

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

答案 4 :(得分:5)

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

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

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

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

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

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

  

在C中,默认情况下,文件范围的变量是extern。如果你使用C,VAL是静态的,ANOTHER_VAL是extern。

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

  

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

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

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

答案 7 :(得分:2)

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

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:1)

const 变量默认情况下是C ++中的静态变量,但是extern 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

答案 10 :(得分:0)

Static会阻止另一个编译单元对该变量进行外部处理,以便编译器可以只在其中“内联”变量的值,而不是为其创建内存存储。

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

答案 11 :(得分:-2)

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