头文件中的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
声明放在头文件中来实现。
全局级别的static
variables只有在它们自己的源文件中才能看到,不pipe它们是通过include还是在主文件中。
编者按:在C ++中,在声明中既没有static
也没有extern
关键字的const
对象是隐式static
。
文件范围variables的static
和extern
标签决定它们是否可以在其他翻译单元(即其他.c
或.cpp
文件)中访问。
-
static
给出了可变的内部链接,隐藏了其他翻译单元。 但是,可以在多个翻译单元中定义具有内部连接的variables。 -
extern
给出了可变的外部链接,使其他翻译单位可见。 通常这意味着variables只能在一个翻译单元中定义。
默认(当你不指定static
或extern
)是C和C ++不同的区域之一。
-
在C中,默认情况下文件作用域variables是
extern
(外部链接)。 如果你使用C,VAL
是static
,ANOTHER_VAL
是extern
。 -
在C ++中,如果文件范围的variables是
const
,则文件范围的variables默认是static
(内部链接),如果不是,则是默认的extern
。 如果你使用C ++,VAL
和ANOTHER_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 ++中的const
variables具有内部链接。 所以,使用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保护来说变得不那么重要了,但是假设头被包含在两个独立的库中,并且应用程序被链接,那么将包括两个实例。