当重构掉一些时,#defines
我在 C++ 头文件中遇到了类似于以下内容的声明:
static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;
问题是,静电会产生什么不同(如果有的话)?#ifndef HEADER
#define HEADER
#endif
请注意,由于经典技巧(如果重要的话),不可能多次包含标题。
VAL
如果标头包含在多个源文件中,静态是否意味着只创建一个副本?
文件范围变量上的static
andextern
标记确定它们是否可在其他翻译单元(即其他.c
或.cpp
文件)中访问。
static
提供变量内部链接,将其隐藏在其他翻译单元中。但是,可以在多个翻译单元中定义具有内部链接的变量。
extern
提供变量外部链接,使其对其他翻译单元可见。通常这意味着变量只能在一个翻译单元中定义。
默认值(当您未指定static
or时extern
)是 C 和 C++ 不同的领域之一。
在 C 中,文件范围的变量extern
默认为(外部链接)。如果您使用的是 C,VAL
则 isstatic
和ANOTHER_VAL
is extern
。
在 C++ 中,如果文件范围的变量是,则默认情况下是static
(内部链接),如果不是const
,则默认情况下是(内部链接) extern
。如果您使用 C++,两者VAL
和ANOTHER_VAL
都是static
.
来自C 规范的草案:
6.2.2 标识符的链接 ... -5- 如果函数的标识符声明没有存储类说明符,则其链接被确定为就好像它是使用存储类说明符 extern 声明的一样。如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的。
来自C++ 规范的草案:
7.1.1 - 存储类说明符 [dcl.stc] ... -6- 在没有存储类说明符的命名空间范围内声明的名称具有外部链接,除非它由于先前的声明而具有内部链接并且如果它不是声明为常量。声明为 const 且未显式声明为 extern 的对象具有内部链接。
这static
意味着将为VAL
包含它的每个源文件创建一个副本。但这也意味着多个包含不会导致多个定义VAL
在链接时发生冲突。在 C 中,如果没有 ,static
您将需要确保仅定义一个源文件VAL
而其他源文件声明它extern
。通常可以通过在源文件中定义它(可能使用初始化程序)并将extern
声明放在头文件中来做到这一点。
static
全局级别的变量仅在它们自己的源文件中可见,无论它们是通过包含到达那里还是在主文件中。
编者注:在 C++ 中,声明中const
既static
没有 orextern
关键字的对象是隐式static
.
静态意味着您每个文件都会获得一份副本,但与其他人不同的是,这样做是完全合法的。您可以使用一个小代码示例轻松地对此进行测试:
测试.h:
static int TEST = 0;
void test();
测试1.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
const
C++ 中的变量具有内部链接。所以,使用static
没有任何效果。
啊
const int i = 10;
一.cpp
#include "a.h"
func()
{
cout << i;
}
二.cpp
#include "a.h"
func1()
{
cout << i;
}
如果这是一个 C 程序,您将收到“多重定义”错误i
(由于外部链接)。
这一层代码的静态声明意味着变量只在当前编译单元中可见。这意味着只有该模块中的代码才能看到该变量。
如果您有一个头文件声明了一个静态变量,并且该头文件包含在多个 C/CPP 文件中,那么该变量将是这些模块的“本地”变量。对于包含标头的 N 个位置,该变量将有 N 个副本。它们完全没有关系。任何这些源文件中的任何代码都只会引用在该模块中声明的变量。
在这种特殊情况下,“静态”关键字似乎没有提供任何好处。我可能会遗漏一些东西,但这似乎并不重要——我以前从未见过这样的事情。
至于内联,在这种情况下,变量可能是内联的,但这只是因为它被声明为 const。编译器可能更有可能内联模块静态变量,但这取决于情况和正在编译的代码。不能保证编译器会内联“静态”。
C书(免费在线)有一章关于链接,更详细地解释了“静态”的含义(尽管其他评论中已经给出了正确答案): http ://publications.gbdirect.co.uk/c_book /chapter4/linkage.html
要回答这个问题,“静态是否意味着只创建一个 VAL 的副本,以防标头包含在多个源文件中?”...
没有。VAL 将始终在包含标头的每个文件中单独定义。
在这种情况下,C 和 C++ 的标准确实会产生差异。
在 C 中,文件范围的变量默认为外部变量。如果您使用 C,VAL 是静态的,ANOTHER_VAL 是外部的。
请注意,如果标头包含在不同的文件中(相同的全局名称定义了两次),现代链接器可能会抱怨 ANOTHER_VAL,并且如果 ANOTHER_VAL 在另一个文件中被初始化为不同的值,肯定会抱怨
在 C++ 中,如果文件范围的变量为 const,则默认为静态,如果不是,则默认为 extern。如果您使用 C++,则 VAL 和 ANOTHER_VAL 都是静态的。
您还需要考虑两个变量都指定为 const 的事实。理想情况下,编译器总是会选择内联这些变量,而不是为它们包含任何存储空间。可以分配存储的原因有很多。我能想到的...
假设这些声明在全局范围内(即不是成员变量),那么:
static表示“内部链接”。在这种情况下,由于它被声明为const,因此编译器可以对其进行优化/内联。如果省略const,则编译器必须在每个编译单元中分配存储空间。
通过省略静态链接默认为外部链接。同样,您已经被const性所拯救 - 编译器可以优化/内联使用。如果您删除const,那么您将在链接时收到多重定义的符号错误。
您不能在不定义静态变量的情况下声明它(这是因为存储类修饰符 static 和 extern 是互斥的)。静态变量可以在头文件中定义,但这会导致包含头文件的每个源文件都有自己的变量私有副本,这可能不是预期的。
const变量在 C++ 中默认是静态的,但在 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
静态阻止另一个编译单元将该变量外部化,以便编译器可以在使用它的地方“内联”变量的值,而不为它创建内存存储。
在您的第二个示例中,编译器不能假定其他一些源文件不会将其外部化,因此它必须将该值实际存储在内存中的某个位置。
静态防止编译器添加多个实例。这对于#ifndef 保护变得不那么重要了,但是假设标头包含在两个单独的库中,并且应用程序是链接的,则将包含两个实例。