一个变量是否可以同时声明为static和extern?

24

为什么以下代码无法编译?

...
extern int i;
static int i;
...

但如果您颠倒顺序,它会编译成功。

...
static int i;
extern int i;
...

这里发生了什么事?


6
你是在询问关于哪种语言,C还是C++?如果你想要得到两种语言的正确答案,请说明。否则,请删除其中一个语言标签。 - Robᵩ
1
@Rob 是的,我想要两个都正确的答案。如果C和C++在这方面表现不同,你会感到惊讶吗? - user1252446
9
@arrows 我一点也不感到惊讶。 - Luchian Grigore
4个回答

20

这在C++标准中是一个特定的例子,当它讨论声明外部或内部链接的复杂性时给出。它在第7.1.1.7节中,引用如下:

static int b ; // b has internal linkage
extern int b ; // b still has internal linkage

extern int d ; // d has external linkage
static int d ; // error: inconsistent linkage

第3.5.6节讨论了在这种情况下extern应该如何行为。

发生的情况是这样的:static int i(在此示例中)是定义,其中static表示i具有内部链接。当static后面出现extern时,编译器看到符号已经存在,并接受它已经具有内部链接并继续执行。这就是为什么您的第二个示例可以编译的原因。

另一方面,extern是一个声明,它隐含地说明符号具有外部链接,但实际上不创建任何内容。由于您的第一个示例中没有i,编译器将i注册为具有外部链接,但当它到达static时,它发现不兼容的语句表明它具有内部链接,并给出错误。

换句话说,这是因为声明比定义“软”。例如,您可以多次声明相同的内容而不出错,但只能定义一次。

我不知道在C中是否相同(但netcoder在下面的答案告诉我们,C标准包含相同的要求)。


它实际上在7.1.1.8节中。 - Luchian Grigore
"第3.5.6节讨论了在这种情况下extern应该如何行为。3.5 / 6只涉及块范围名称。我找不到任何关于命名空间范围名称的类似规定。" - dyp

11
对于 C 语言,引用标准,C11 6.2.2:“标识符的链接性”:
3)如果对象或函数的文件范围标识符的声明包含存储类别说明符 static,则该标识符具有内部链接。
4)对于在先前声明可见的作用域中使用存储类别说明符 extern 声明的标识符,如果先前声明指定了内部或外部链接,则后续声明中标识符的链接与先前声明中指定的链接相同。 如果没有先前的声明可见,或者先前的声明未指定链接,则标识符具有外部链接。
(强调是我的)
这解释了第二个例子(i将具有内部链接)。至于第一个例子,我十分确定其行为未定义:
7)如果在一个翻译单元内,同一标识符出现具有内部和外部链接,则行为未定义。
因为extern出现在带内部链接的标识符被声明之前,6.2.2/4不适用。因此,i具有内部和外部链接,因此会导致行为未定义。
如果编译器发出诊断信息,那么你很幸运。它也可能编译两个程序而没有错误,并仍符合标准。

1

C++:

7.1.1 存储类说明符 [dcl.stc]

7) 在命名空间作用域中声明的名称,如果没有存储类说明符,则具有外部链接性,除非它因先前的声明而具有内部链接性,并且只要它没有被声明为 const。声明为 const 且未显式声明为 extern 的对象具有内部链接性。

因此,第一个尝试首先给 i 外部链接性,然后再给它内部链接性。

第二个则首先给它内部链接性,第二行不尝试给它外部链接性,因为它之前已经被声明为内部链接性。

8) 对于给定实体的连续声明所暗示的链接必须一致。也就是说,在给定作用域内,每个声明相同变量名或函数名的重载都应该暗示相同的链接。但是,在给定一组重载函数中,每个函数可以具有不同的链接。
[ 示例:

[...]
static int b; // b has internal linkage
extern int b; // b still has internal linkage
[...]
extern int d; // d has external linkage
static int d; // error: inconsistent linkage
[...]

全局作用域也是命名空间作用域吗? - user1252446
@arrows 是全局命名空间。 - Luchian Grigore

1
在Microsoft Visual Studio中,这两个版本都可以编译通过。但在Gnu C++中会出现错误。
我不确定哪个编译器是“正确”的。无论如何,同时拥有这两行并没有太多意义。 extern int i表示整数i在某个其他模块(对象文件或库)中被定义。这是一个声明。编译器不会在此对象中分配存储空间,但它将在程序的其他地方使用变量时识别该变量。 int i告诉编译器为i分配存储空间。这是一个定义。如果其他C++(或C)文件有int i,链接器将抱怨int i被定义了两次。 static int i与上述类似,具有额外的功能,即i是本地的。即使它们声明extern int i,也无法从其他模块访问它。人们在这种情况下使用关键字static来保持i的本地化。
因此,在其他地方已经声明了i,并且在模块内部定义为静态的,似乎是一个错误。Visual Studio对此保持沉默,而g++只在特定顺序下保持沉默,但无论如何,您都不应该在同一源代码中拥有这两行。

3
我不确定哪个编译器是“正确”的。当MSVC与G++意见不一致时,世界上没有人会因打赌它错而赔钱 ;) - Jonathan Wakely
4
这两个编译器在这种情况下都是正确的。第一个示例存在未定义行为,因此编译器不需要发出诊断警告。而第二个示例符合标准要求。 - netcoder

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