我很惊讶没有人引用C11规范。抱歉引用了这么长的内容,但我认为它是相关的。
7.5 错误
头文件<errno.h>
定义了几个宏...
...以及errno
errno
扩展为一个可修改的lvalue(201),其类型为int
并具有线程本地存储期,多个库函数会将其值设置为正错误号码。如果压制宏定义以访问实际对象,或者程序定义了名称为errno
的标识符,则行为是未定义的。
在程序启动时,初始线程中errno
的值为零(其他线程中errno
的初始值为不确定值),但是任何库函数都不会将其设置为零。(202)即使没有错误,库函数调用也可能将errno
的值设置为非零值,前提是该函数的描述中未记录使用errno
。
(201) 宏errno
不必是对象的标识符。它可以扩展为从函数调用中得到的可修改的lvalue(例如*errno()
)。
(202) 因此,对于使用errno
进行错误检查的程序,应在调用库函数之前将其设置为零,然后在随后的库函数调用之前检查它。当然,库函数可以在进入时保存errno
的值,然后将其设置为零,只要在返回之前如果errno
的值仍然为零,则恢复原始值即可。
"Thread local" 意味着
register
不再使用。 类型
int
意味着位域不再使用(依我看)。 因此,
&errno
在我看来是合法的。
持久使用诸如 "它" 和 "
the value" 等词语表明标准的作者并没有考虑到
&errno
是非常数的。 我想人们可以想象一种实现,在特定线程内
&errno
不是常量,但为了按照脚注所说的方式使用它(设置为零,然后在调用库函数后检查),必须是有意对抗的,并且可能需要专门的编译器支持才能对抗。
简而言之,如果规范允许非常量的
&errno
,我认为这不是故意的。
[更新]
R. 在评论中提出了一个很好的问题。 经过思考,我相信我现在知道了他的问题和原始问题的正确答案。 让我看看是否能说服你,亲爱的读者。
R. 指出 GCC 允许在顶层使用类似以下内容的东西:
register int errno asm ("r37"); // line R
这将声明
errno
为一个在寄存器
r37
中保持的全局值。显然,它将是一个可修改的线程本地lvalue。那么,符合C实现能像这样声明
errno
吗?
答案是否定的。当你或我使用“声明”这个词时,我们通常会有一个口语和直观的概念。但标准并不以口语或直观的方式进行表述; 它精确地讲话,并且只旨在使用定义明确的术语。在“声明”的情况下,标准本身定义了这个术语; 当它使用该术语时,它正在使用自己的定义。
通过阅读规范,您可以了解“声明”是什么,以及它不是什么。换句话说,标准描述了语言“C”。它没有描述“不是C的某种语言”。就标准而言,“具有扩展功能的C”只是“不是C的某种语言”。
因此,从标准的角度来看,R行根本不是声明。它甚至无法解析!它可能与以下内容相同:
long long long __Foo_e!r!r!n!o()blurfl??/**
就规范而言,这与R行一样只是一个“声明”,也就是说,根本不算声明。
因此,当C11规范在第6.5.3.2节中说:
“一元&运算符的操作数应该是函数指示器、[]或一元*运算符的结果,或者是指定了不是位域并且没有使用register存储类说明符声明的对象的lvalue。”
它意味着非常精确的事情,与R行之类的东西无关。
现在,考虑errno所引用的int对象的声明。(注意:我指的不是errno名称的声明,因为如果errno是宏,则可能没有这样的声明。我指的是底层int对象的声明。)
上述语言表示,除非它指示了一个位域或指示了一个使用了“register”存储类说明符的对象,否则您可以获取lvalue的地址。底层errno对象的规范说明它是具有线程本地持续时间的可修改int lvalue。现在,的确规范没有说底层errno对象必须被声明。也许它只是通过一些实现定义的编译器魔法出现。但再次地,当规范说“使用了寄存器存储类说明符进行了声明”时,它正在使用自己的术语。因此,底层errno对象要么在标准意义下被“声明”,在这种情况下它不能同时是register和thread-local;要么根本没有被声明,这种情况下它就不是register声明的。无论哪种方式,由于它是lvalue,您都可以获取其地址。(除非它是位域,但我认为我们都同意位域不是int类型的对象。)
(union {signed int x:32;}){0}.x
的类型是什么?(假设32是int
的宽度;根据需要进行替换)。 - R.. GitHub STOP HELPING ICEregister
存储类别获取变量的地址,但它们仍然可以用作lvalue。 - Kerrek SB