头文件中的variables声明 – 是否是静态的?

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

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

问题是,静态会有什么不同呢? 请注意,由于经典的#ifndef HEADER #define HEADER #endif技巧(如果有的话),多个包含头文件是不可能的。

静态意味着只创build一个VAL副本,以防头文件被多个源文件包含在内?

static意味着将为其包含的每个源文件创build一个VAL副本。但是这也意味着多个包含将不会导致在链接时碰撞的VAL多个定义。 在C中,如果没有static你需要确保只有一个源文件定义了VAL而其他的源文件声明了这个文件是extern 。 通常可以通过在源文件中定义它(可能使用初始值设定项)并将extern声明放在头文件中来实现。

全局级别的staticvariables只有在它们自己的源文件中才能看到,不pipe它们是通过include还是在主文件中。


编者按:在C ++中,在声明中既没有static也没有extern关键字的const对象是隐式static

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

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

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

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

  • 在C中,默认情况下文件作用域variables是extern (外部链接)。 如果你使用C, VALstaticANOTHER_VALextern

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

从C规范的草案:

6.2.2标识符的链接…如果一个函数的标识符的声明没有存储类说明符,那么它的链接就像使用存储类说明符extern声明一样。 如果对象的标识符的声明具有文件范围并且没有存储类说明符,则其链接是外部的。

从C ++规范草案:

7.1.1 – 存储类别说明符[dcl.stc] … – 在没有存储类别说明符的名称空间范围内声明的名称具有外部链接,除非由于之前的声明而具有内部链接,并且提供的不是声明const。 对象声明为const并且没有明确声明为extern具有内部链接。

静态意味着你得到一个文件的副本,但不像其他人所说,这样做是完全合法的。 你可以用一个小的代码示例轻松地testing这个:

test.h:

 static int TEST = 0; void test(); 

test1.cpp:

 #include <iostream> #include "test.h" int main(void) { std::cout << &TEST << std::endl; test(); } 

testing2.cpp:

 #include <iostream> #include "test.h" void test() { std::cout << &TEST << std::endl; } 

运行这个给你这个输出:

0x446020
0x446040

C ++中的constvariables具有内部链接。 所以,使用static不起作用。

 const int i = 10; 

one.cpp

 #include "ah" func() { cout << i; } 

two.cpp

 #include "ah" func1() { cout << i; } 

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

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

如果你有一个声明一个variablesstatic的头文件,并且这个头文件被包含在多个C / CPP文件中,那么这个variables对于这些模块将是“本地的”。 包含标题的N个地方将会有N个副本。 他们根本就没有关系。 任何这些源文件中的任何代码将只引用在该模块中声明的variables。

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

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

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

要回答这个问题,“静态是否意味着只有一个VAL副本被创build,以防头文件包含多个源文件?”…

NO 。 VAL将始终在包含标题的每个文件中分别定义。

在这种情况下,C和C ++的标准确实有所不同。

在C中,文件作用域variables默认是extern。 如果你使用C,VAL是静态的,ANOTHER_VAL是外部的。

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

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

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

  • debugging选项
  • 在文件中的地址
  • 编译器总是分配存储空间(复杂的consttypes不能很容易地被内联,所以对于基本types来说就成了一个特例)

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

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

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

通过省略静态链接是默认的extern 。 再次,你已经被保存了 – 编译器可以优化/内联使用。 如果你删除这个常量,那么在链接时你会得到一个多重定义的符号错误。

constvariables在C ++中默认是静态的,但是是extern C。所以如果你使用C ++,这个没什么用处。

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

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

 bruziuz:~/test$ cat ac const int b = 22; int main(){return 0;} bruziuz:~/test$ cat bc const int b=2; bruziuz:~/test$ gcc -xc -std=c89 ac bc /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 ac bc bruziuz:~/test$ bruziuz:~/test$ gcc --version | head -n1 gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609 

静态可以防止另一个编译单元使该variables出现,以便编译器可以将variables的值“内嵌”到variables的使用位置,而不是为其创build内存存储。

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

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