命名空间中的静态变量与非静态变量的区别

63

我有一个名为foo的命名空间,其中包含一个整数变量bar,在foo.h中声明。

namespace foo {
    int bar;
}

如果我只在一个文件中包含foo.h,那么这个方法完美地运行。但是当我从两个或多个文件中包含foo.h时,就会出现链接器错误。我发现如果我将bar声明为static,我就可以在多个文件中包含foo.h。这对我来说很奇怪,因为我不知道在命名空间中可以声明静态变量(这是什么意思?)。

这是为什么?更重要的是,为什么没有使用static会导致错误?在namespace中使用static时有什么含义?

4个回答

61

在不同的上下文中,static 有多个含义。在这个特定的上下文中,它意味着变量具有内部链接性,因此包括该头文件的每个翻译单元都将有其自己的变量副本。

请注意,虽然这将消除链接器错误,但是这将为生成的每个目标文件维护一个单独的 foo::bar 变量(对不同的目标文件之间的更改是不可见的)。

如果您想要一个单一的变量,您应该在头文件中声明它为 extern 并在一个翻译单元中提供一个单一的定义。


43

当你把变量声明为 static 时,它的作用域仅限于给定的翻译单元。如果没有使用 static,它的作用域将是全局的。

如果你在一个 .h 文件中(无论是否在 namespace 内或外)声明一个 static 变量,并在各个 .cpp 文件中包含该头文件,则 static 变量将在每个 .cpp 文件中具有局部作用域。因此,现在,包含该头文件的每个 .cpp 文件将有自己的变量副本。

如果没有使用 static 关键字,编译器将只生成一个该变量的副本,因此,当你在多个 .cpp 文件中包含头文件时,链接器将抱怨多个定义。


1
但是如果头文件有包含保护,则编译器只会包含一次头文件。在这种情况下,静态变量和非静态变量是否相同? - Ben Butterworth
@ButterHub,预处理阶段使用包含保护来确保文件仅被包含一次。而静态变量的创建是在编译阶段进行的。解决包含保护后,无论头文件在哪里可见 --> 静态变量都会在所有这些.cpp文件中创建。 - iammilind
但是在预处理器用File.h的内容替换了#include "File.h"后,任何C++文件中都不再显示头文件了。编译器如何知道为多个文件创建静态变量?(第一个文件将包含内容,但是包含保护防止其他文件获取它吗?) - Ben Butterworth
1
@ButterHub,这可能很难在评论中解释清楚。您可以创建一个带有包含保护的test.h文件,并将其包含在test1.cpp、test2.cpp、testN.cpp中。然后使用g++的-E选项编译它们,以查看每个.cpp文件中变量的声明方式。例如,“g++ -E test1.cpp > test1_show”。 - iammilind
2
感谢@iammilind。我对预处理器如何使用include guards的误解是问题所在。尽管有包含保护,但如果被'#include',那么代码会在每个testX.cpp文件中被替换一次。 include guards的存在是为了防止同一文件中的重复定义。 - Ben Butterworth

7

问题是由于该变量有多个定义引起的。在不同的翻译单元中的定义会相互冲突,就像多个非内联函数定义不能正常工作一样。

当您将变量设置为静态时,您正在给变量提供内部链接,因此每个翻译单元都有其自己独立的副本。

您实际上可能想要做的是仅在头文件中放置声明(使用extern),然后将定义放在实现文件中。


2
还要注意,在C++的命名空间(全局)作用域中,const int默认会隐式添加static关键字:在C++头文件中定义常量变量 为了更好地理解发生了什么,请对编译的中间ELF目标文件进行readelf操作,您将清楚地看到符号是否被定义两次。以下是详细的示例:C语言中"static"关键字的含义

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接