头文件中的变量声明-静态还是非静态?

时间:2020-03-06 14:21:18  来源:igfitidea点击:

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

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

问题是,静电会产生什么区别(如果有的话)?请注意,由于经典的#ifndef HEADER#define HEADER#endif`技巧(如果很重要),不可能多次包含标头。

如果头文件被多个源文件包含,则静态值是否仅创建一个" VAL"副本?

解决方案

静态的阻止另一个编译单元替换该变量,以便编译器仅可以在使用该变量的位置"内联"该变量的值,而无需为其创建内存存储。

在第二个示例中,编译器无法假定其他源文件不会终止它,因此它实际上必须将该值存储在内存中的某个位置。

静态阻止编译器添加多个实例。使用#ifndef保护,这变得不那么重要了,但是假设标头包含在两个单独的库中,并且应用程序已链接,则将包含两个实例。

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

全局级别的"静态"变量仅在其自己的源文件中可见,无论它们是通过include到达还是在主文件中。

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

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

如果我们有一个头文件声明了一个静态变量,并且该头文件包含在多个C / CPP文件中,则该变量对于这些模块将是"本地"的。对于包含标头的N个位置,该变量将有N个副本。它们根本不相关。这些源文件中任何一个中的任何代码都只会引用该模块中声明的变量。

在这种特殊情况下,"静态"关键字似乎没有提供任何好处。我可能会丢失一些东西,但这似乎无关紧要-以前我从未见过这样的事情。

至于内联,在这种情况下,该变量很可能是内联的,但这仅是因为它已声明为const。编译器可能更可能内联模块静态变量,但这取决于情况和正在编译的代码。不能保证编译器会内联" statics"。

假设这些声明在全局范围内(即不是成员变量),则:

静态意味着"内部联系"。在这种情况下,由于将其声明为const,因此编译器可以对其进行优化/内联。如果省略const,则编译器必须在每个编译单元中分配存储。

通过省略静态,默认情况下链接是外部的。同样,编译器可以优化/内联用法的常量性已使我们省去了。如果删除const,则在链接时会出现乘法定义的符号错误。

静态表示每个文件一个副本,但是与其他文件不同,这样做是完全合法的。我们可以使用一个小的代码示例轻松地对此进行测试:

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

运行此命令可以得到以下输出:

0x446020

  0x446040

C语言书籍(在线免费)上有一个有关链接的章节,它更详细地解释了"静态"的含义(尽管其他注释中已经给出了正确的答案):
http://publications.gbdirect.co.uk/c_book/chapter4/linkage.html

文件作用域变量上的" static"和" extern"标记确定它们是否可以在其他翻译单元(即其他" .c"或者" .cpp"文件)中访问。

  • "静态"给出变量内部链接,将其隐藏在其他翻译单元中。但是,具有内部链接的变量可以在多个翻译单元中定义。
  • extern提供可变的外部链接,使其对其他翻译单元可见。通常,这意味着只能在一个转换单元中定义变量。

默认值(当我们不指定static或者extern时)是C和C ++不同的区域之一。

  • 在C语言中,文件作用域变量默认为extern(外部链接)。如果我们使用的是C,则VAL是静态的,而ANOTHER_VAL是extern的。
  • 在C ++中,文件作用域变量如果为const则默认为"静态"(内部链接),否则为默认" extern"。如果我们使用的是C ++,则VAL和ANOTHER_VAL都是静态的。

从C规范的草稿中:

6.2.2 Linkages of identifiers
  ... 
  -5- If the declaration of an identifier for a function has no storage-class specifier, its linkage
  is determined exactly as if it were declared with the storage-class specifier extern. If
  the declaration of an identifier for an object has file scope and no storage-class specifier,
  its linkage is external.

根据C ++规范的草案:

7.1.1 - Storage class specifiers [dcl.stc]
  ...
  -6- A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared const. Objects declared const and not explicitly declared extern have internal linkage.

要回答这个问题,"如果在多个源文件中包含标头,则静态是否仅创建一个VAL副本?" ...

不。 VAL将始终在每个包含标头的文件中单独定义。

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

In C, file-scoped variables are extern by default. If you're using C, VAL is static and ANOTHER_VAL is extern.

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

In C++, file-scoped variables are static by default if they are const, and extern by default if they are not. If you're using C++, both VAL and ANOTHER_VAL are static.

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

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

C ++中的const变量具有内部链接。因此,使用"静态"无效。

const int i = 10;

一个cpp

#include "a.h"

func()
{
   cout << i;
}

二.cpp

#include "a.h"

func1()
{
   cout << i;
}

如果这是C程序,则将由于i的错误而出现"多个定义"错误(由于外部链接)。