23

为什么以下不编译?

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

但如果你颠倒顺序,它编译得很好。

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

这里发生了什么?

4

4 回答 4

19

当 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具有内部链接。当编译器看到符号已经存在并接受它已经具有内部链接并继续时发生externstatic这就是您的第二个示例编译的原因。

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

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

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

于 2013-01-18T17:17:45.413 回答
11

对于 C,在 C11 6.2.2 中引用标准:标识符的链接

3) 如果对象或函数的文件范围标识符的声明包含存储类说明符static,则该标识符具有内部链接。

4)对于在该标识符的先前声明可见的范围内使用存储类说明符extern声明的标识符,如果先前声明指定了内部或外部链接,则后面声明的标识符的链接与该标识符的链接相同之前声明中指定的链接。如果前面的声明不可见,或者前面的声明没有指定链接,则标识符具有外部链接。

(强调我的)

这解释了第二个示例(i将具有内部链接)。至于第一个,我很确定这是未定义的行为:

7) 如果在翻译单元内,相同的标识符同时出现在内部和外部链接中,则行为未定义。

...因为extern出现在使用内部链接声明标识符之前,6.2.2/4 不适用。因此,i既有内联也有外联,所以是UB。

如果编译器发出诊断,我猜你很幸运。它可以无错误地编译并且仍然符合标准。

于 2013-01-18T17:22:21.140 回答
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
[...]
于 2013-01-18T17:20:15.893 回答
0

在 Microsoft Visual Studio 中,两个版本都编译得很好。在 Gnu C++ 上你会得到一个错误。

我不确定哪个编译器是“正确的”。无论哪种方式,拥有两条线都没有多大意义。

extern int i表示整数i是在其他模块(目标文件或库)中定义的。这是一个声明。编译器不会在此对象中分配存储空间i,但当您在程序中的其他位置使用该变量时,它会识别该变量。

int i告诉编译器为i. 这是一个定义。如果其他 C++(或 C)文件有int i,链接器会抱怨 int i 被定义了两次。

static int i与上面类似,具有i本地的额外功能。它不能从其他模块访问,即使它们声明extern int i. 人们正在使用关键字 static(在这种情况下)来保持 i 本地化。

因此,将i两者都声明为在其他地方定义,并且在模块中定义为静态似乎是一个错误。Visual Studio 对此保持沉默,而 g++ 仅按特定顺序保持沉默,但无论哪种方式,您都不应该在同一源代码中同时包含这两行。

于 2013-01-18T17:19:02.453 回答