当重构一些#defines
时,我遇到了类似于C ++头文件中的以下声明:
static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;
问题是,静态会产生什么区别?请注意,由于经典的#ifndef HEADER
#define HEADER
#endif
技巧(如果重要),无法多次包含标题。
如果标题包含在多个源文件中,静态是否只创建VAL
的一个副本?
答案 0 :(得分:106)
文件范围变量上的static
和extern
标记确定它们是否可以在其他翻译单元(即其他.c
或.cpp
文件)中访问。
static
提供变量内部链接,将其与其他翻译单元隐藏。但是,具有内部链接的变量可以在多个翻译单元中定义。
extern
提供变量外部链接,使其对其他翻译单元可见。通常,这意味着变量必须仅在一个翻译单元中定义。
默认值(当您未指定static
或extern
时)是C和C ++不同的区域之一。
在C中,默认情况下,文件范围的变量为extern
(外部链接)。如果您使用的是C,则VAL
为static
而ANOTHER_VAL
为extern
。
在C ++中,文件范围的变量默认为static
(内部链接),如果它们是const
,则默认为extern
,如果不是VAL
。如果您使用的是C ++,ANOTHER_VAL
和static
都是{{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 ++中,声明中static
和const
关键字的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)
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的事实。理想情况下,编译器总是选择内联这些变量,而不是为它们包含任何存储。存储可以分配的原因有很多。我能想到的......
答案 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保护来说变得不那么重要了,但假设标题包含在两个单独的库中,并且应用程序已链接,则会包含两个实例。