对于使用extern的新声明,如何解决多个先前声明?

第三个x应该引用什么:

 #include  static char x = '1'; int main(void) { char x = '2'; { extern char x; printf("%c\n", x); } } 

这出现在这个答案中 ,并且:

  • 在Apple LLVM 9.1.0 clang-902-0.39.2中, extern char x表示第一个x ,并且打印“1”。
  • GCC 8.2不接受此源文本。 ,抱怨:“错误:变量先前声明’静态’重新声明’extern’”。

C 2018 6.2.2 4说:

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

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

  • …如果先前的声明指定内部或外部链接,则后面声明中的标识符的链接与先前声明中指定的链接相同。
  • …如果先前声明未指定链接,则标识符具有外部链接。

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

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

第三个声明extern char x应该使用外部链接声明x ,基于C 2018 6.2.2 4,其中说:

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

在声明extern char xextern char 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和TC通过他们的评论告知我对此的看法。)