如何为 extern 的新声明解决多个先前声明?

Posted

技术标签:

【中文标题】如何为 extern 的新声明解决多个先前声明?【英文标题】:How are multiple prior declarations resolved for a new declaration with extern? 【发布时间】:2019-03-23 09:46:09 【问题描述】:

第三个x应该指什么:

#include <stdio.h>

static char x = '1';

int main(void)

    char x = '2';
    
        extern char x;
        printf("%c\n", x);
    

这发生在this answer,并且:

在Apple LLVM 9.1.0 clang-902-0.39.2 中,extern char xx 指的是第一个x,并且打印了“1”。 GCC 8.2 does not accept this source text.,抱怨:“错误:以前声明的变量 'static' 重新声明了 'extern'”。

C 2018 6.2.2 4 说:

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

由于x 有两个先前声明,以下每个“if”子句的条件都为真,第一个用于第一个先前声明,第二个用于第二个先前声明:

…如果前面的声明指定了内部或外部链接,则后面声明的标识符的链接与前面声明的链接相同。 …如果前面的声明没有指定链接,则标识符有外部链接。

Clang 在这里的行为与使用第一个子句一致,因此第三个x 具有内部链接,并与第一个x 引用相同的对象。 GCC在这里的行为与使用第二个子句一致,因此第三个x具有外部链接,并与第一个x冲突,后者具有内部链接。

C 标准是否为我们提供了一种解决问题的方法?

【问题讨论】:

有趣的是,clang V8 抱怨 char x = '2'; 没有被使用(所以它显然使用了静态变量。(使用 C++ 和 g++ V9 编译器会抱怨两个变量都未被使用和链接器抛出 undefined reference to 'x' 错误 :D) 我认为您没有引用的 C11 注释 31 可能是相关的。 For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible,31) 其中注释 31 说 31) As specified in 6.2.1, the later declaration might hide the prior declaration. 这是这里的情况。 6.2.1是正常范围规则。在这种情况下,extern 应该引用没有链接的局部变量。 @Lundin - 我认为这就是答案,你应该在这样的地方发帖。问题的症结似乎是x 在文件范围内的可见性。 @Lundin - 注意不是,但 6.2.1p4 中的最后一句话是。 “在内部范围内,标识符指定在内部范围内声明的实体;在外部范围内声明的实体在内部范围内是隐藏的(不可见)。” 我将添加 p7 然后声明未定义的行为。所以clang在技术上并没有错…… 【参考方案1】:

第三个声明 extern char x 应该声明 x 具有外部链接,基于 C 2018 6.2.2 4,它说:

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

在声明extern char x 处,x 的第一个声明不可见,因为它已被第二个声明隐藏。因此,它不符合“该标识符的事先声明是可见的”的条件。 x 的第二个声明是可见的,因此对于上一段而言,它是“预先声明”。

那么最后一句应该控制:前面的声明指定没有链接(6.2.2 6,没有extern的块范围标识符没有链接),所以第三个x有外部链接。

那么违反了 6.2.2 7 因为第一个 x 有内部链接,而第三个 x 有外部链接:

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

由于没有违反语法规则或约束,因此标准不要求 C 实现报告诊断。由于行为未定义,它可以做任何事情,包括接受此代码并使第三个x 引用与第一个x 相同的对象。因此,Clang 和 GCC 的行为在这方面都没有违反标准。但是,由于违反了 6.2.2 7,因此可能首选诊断程序,而没有诊断程序可能会被认为是 Clang 的缺陷。

(感谢 Paul Ogilvie 和 T.C. 用他们的 cmets 告知我对此的想法。)

【讨论】:

恕我直言,最好用结论打开答案;第一段似乎暗示代码声明 x 带有外部链接(实际上并非如此,因为后来证明整个程序都是 UB) @RobertSsupportsMonicaCellio:关于“为什么文件范围 x 的链接在被 x 的第二个声明隐藏/隐藏时修改了...”:错误消息没有说联动被修改。它说之前用“static”声明的x被重新声明为“extern”。 “外部”是一种联系方式。 “外部”是一个关键字。它们是不同的东西,尽管它们是相关的。该消息只是说有一个x 的新声明具有extern,而之前的声明具有static。这不是一个好消息,因为编译器的实际原因是…… ... 抱怨不是 x 被重新声明,而是新声明违反了 6.2.2 7,如本答案所述。尽管如此,它关于新声明具有“extern”而先前声明具有“static”的说法是正确的。 x 被重新声明为“在文件范围内”。它只是说第三个声明是x 的重新声明。那是真实的。有一个更早的声明和一个后来的声明,所以有一个重新声明。范围无关紧要;我们只是说有另一个声明与之前的声明具有相同的标识符。 @RobertSsupportsMonicaCellio:如果第二个声明不存在,C 允许 extern char x 声明。 6.2.2 7 没有被违反,因为 6.2.2 4 表示由 extern char x 产生的链接是先前声明的链接,因此它 x 将具有来自原始 static 的内部链接。你可以认为extern这里不代表“外联”;它的意思是“这个声明指的是一个在这个范围之外的x。”当它所指的xstatic x 时,这很好,因为 6.2.2 4 表示第一个声明是否具有内部链接,...

以上是关于如何为 extern 的新声明解决多个先前声明?的主要内容,如果未能解决你的问题,请参考以下文章

Google Play:未显示用于删除先前声明的新 APK 权限的表单字段“合规状态”

变量定义与变量声明

对于 json-schema,我如何为 json 声明一个类型

如何为 Rest Assured 3.0.3(使用 Java 和 TestNG)声明默认解析器?

如何为闭包参数声明生命周期?

如何为函数(C++)声明字符序列?