如何为 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 x
的x
指的是第一个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
。”当它所指的x
是static
x
时,这很好,因为 6.2.2 4 表示第一个声明是否具有内部链接,...以上是关于如何为 extern 的新声明解决多个先前声明?的主要内容,如果未能解决你的问题,请参考以下文章
Google Play:未显示用于删除先前声明的新 APK 权限的表单字段“合规状态”
对于 json-schema,我如何为 json 声明一个类型