是&errno合法C?

每7.5,

[errno]扩展为一个可修改的lvalue175),其types为int,其值由多个库函数设置为正确的错误编号。 errno是macros还是用外部链接声明的标识符是未指定的。 如果为了访问实际对象而抑制macros定义,或者程序定义了名称为errno的标识符,则行为是不确定的。

macroserrno不一定是一个对象的标识符。 它可能会扩展为由函数调用(例如,* errno())产生的可修改的左值。

我不清楚这是否足以要求&errno不是违反约束。 C语言有左值(比如register-storage-classvariables;但是这些variables只能是自动的,所以errno不能被定义成这样), &操作符是违反约束条件的。

如果&errno是合法的C,是否需要保持不变?

所以§6.5.3.2p1指定

一元&运算符的操作数应该是函数标识符,[]或一元运算符的结果,或者是一个左值,它指定一个不是位域的对象,并且不用寄存器存储类说明符声明。

认为这可以被认为&lvalue对任何不在这两个类别中的&lvalue都是正确的。 正如你所提到的, errno不能用寄存器存储类说明符来声明,我认为(尽pipe现在没有追踪引用来检查)你不能拥有一个inttypes的位域。

所以我认为规范要求&(errno)是合法的C.

如果&errno是合法的C,是否需要保持不变?

据我所知,允许errno成为一个macros的一部分(以及它在glibc中的原因)是允许它作为线程本地存储的引用,在这种情况下,它肯定不会是恒定的线程。 我不认为有任何理由期望它必须是不变的。 只要errno的价值保留指定的语义,我认为没有任何理由一个不正当的C库不能改变&errno在程序过程中引用不同的内存地址 – 例如,每次设置时释放和重新分配后备存储errno

你可以想象维护由库设置的最后N个errno值的环形缓冲区,并且&errno总是指向最新的。 我不认为它会特别有用,但是我看不出违反规范的任何方式。

我很惊讶没有人引用C11规范呢。 对于这个长句引用道歉,但我相信这是相关的。

7.5错误

标题定义了几个macros…

…和

errno

其扩展为具有int型和线程局部存储持续时间的可修改的左值(201),其值由多个库函数设置为正误差值。 如果为了访问实际对象而抑制macros定义,或者程序定义了名称为errno的标识符,则行为是不确定的。

程序启动时errno值为零(其他线程中errno的初始值是一个不确定的值),但是从不会被任何库函数设置为零(202)errno的值可能被设置为如果在本标准的function描述中没有loggingerrno的使用,则由库函数调用是否存在错误。

(201)macroserrno不必是对象的标识符。 它可能会扩展为由函数调用(例如, *errno() )产生的可修改的左值。

(202)因此,使用errno进行错误检查的程序应该在调用库函数之前将其设置为零,然后在后续的库函数调用之前对其进行检查。 当然,一个库函数可以在input时保存errno的值,然后将其设置为零,只要在返回之前errno的值仍然为零,只要原始值被恢复。

“本地线程”表示register不存在。 typesint意味着位域已经不存在(IMO)。 所以&errno对我来说看起来合法。

持续使用诸如“it”和“value”等词语表明标准的作者并不认为&errno是非恒定的。 我想可以设想一个实现,其中&errno在特定的线程内不是常量,而是以脚注的方式使用(设置为零,然后在调用库函数之后检查),它必须是故意对抗的,并且可能需要专门的编译器支持只是为了对抗。

总之,如果规范确实允许一个非常量&errno ,我不认为这是故意的。

[更新]

R.在评论中提出了一个很好的问题。 思考过后,我相信我现在知道他的问题和原来问题的正确答案了。 亲爱的读者,让我看看我能否说服你。

R.指出,GCC允许在顶层这样的东西:

 register int errno asm ("r37"); // line R 

这将宣布errno作为r37注册的全球价值。 显然,这将是一个线程局部可修改的左值。 那么,符合C的实现是否可以像这样声明errno

答案是否定的 。 当你或我使用“声明”这个词时,我们通常会有一个口语和直觉的概念。 但是标准不是口语化或直观化的; 它精确地说 ,而且它的目的只是使用明确的术语。 在“声明”的情况下,标准本身定义了这个术语; 当它使用这个术语时,它使用它自己的定义。

通过阅读规范,你可以准确地知道“声明”是什么,而不是什么。 换句话说,标准描述了语言“C”。 它没有描述“一些不是C的语言”。 就标准而言,“带扩展名的C”只是“某种不是C的语言”。

因此,从标准的angular度来看,R线并不是一个声明 。 它甚至不parsing! 它可能会被读作:

 long long long __Foo_e!r!r!n!o()blurfl??/** 

就规格而言,这与R线一样是一个“声明”; 即,根本不是。

所以,当C11规格说,在第6.5.3.2节:

一元&运算符的操作数应该是函数标识符, []或一元运算符的结果,或者是一个指定不是位域的对象的左值,并且不用寄存器存储类指定器。

…这意味着一些非常精确的东西, 而不是像R线那样的东西。

现在,考虑errno引用的int 对象的声明。 (注意:我不是指errno 名字的声明,因为当然,如果errno是一个macros,那么就没有这个声明,我的意思是声明底层的int对象。)

上面的语言说,除非它指定了一个位域或者它指定了一个对象“声明”的register否则你可以取一个左值的地址。 底层errno对象的规范说这是一个带有线程本地持续时间的可修改的int左值。

现在,规范并没有说,根本的errno对象必须被声明。 也许它只是通过一些实现定义的编译器魔术出现。 但是,当规范说“用寄存器存储类说明符声明”时,它使用自己的术语。

因此,无论是基本的errno对象是标准意义上的“声明”,在这种情况下,它不能既是register又是线程本地的; 或者根本没有声明,在这种情况下它没有被声明为register 。 无论哪种方式,因为它是一个左值,你可以采取它的地址。

(除非是位域,但我认为我们同意位域不是inttypes的对象。)

errno的原始实现是一个全局的intvariables,各种标准C库组件在遇到错误时用来指示错误值。 然而,即使在那些日子里,人们必须小心可重入的代码,或者使用库函数调用,可以将errno为与您处理错误时不同的值。 通常情况下,如果由于某些其他函数或代码片段可能显式地或通过库函数调用来errno的值的可能性,任何时间长度都需要错误代码,则会将值保存在临时variables中。

因此,在这个原始的全局int实现中,使用运算符的地址并且依赖于地址保持不变的原则实现了库的结构。

但是,对于multithreading,不再有一个单一的全球性,因为有一个单一的全局是不是线程安全的。 所以有线程本地存储的想法也许使用一个函数,返回一个指向分配区域的指针。 所以你可能会看到一个如下完全虚构的例子:

 #define errno (*myErrno()) typedef struct { // various memory areas for thread local stuff int myErrNo; // more memory areas for thread local stuff } ThreadLocalData; ThreadLocalData *getMyThreadData () { ThreadLocalData *pThreadData = 0; // placeholder for the real thing // locate the thread local data for the current thread through some means // then return a pointer to this thread's local data for the C run time return pThreadData; } int *myErrno () { return &(getMyThreadData()->myErrNo); } 

然后, errno会被用作errno = 0;的单个全局variables而不是线程安全的intvariableserrno = 0; 或者像if (errno == 22) { // handle the error ,甚至像int *pErrno = &errno; 。 这一切都是有效的,因为最终线程本地数据区被分配并保持不动,并且使得errno看起来像一个extern int的macros定义隐藏了其实际实现的pipe道。

我们不想要的一件事情是在访问值的时候, errno的地址在某个线程的时间片之间突然发生某种dynamic分配,克隆,删除序列的切换。 当你的时间片到了,它已经到了,除非你有某种types的同步,或者在你的时间片到期之后保持CPU的某种方式,让线程局部区域移动似乎是一个非常冒险的命题。

这又意味着你可以依赖于运算符的地址给你一个特定线程的常量值,尽pipe线程之间的常量值是不同的。 我可以很好地看到使用errno地址的库,以减less每次调用库函数时执行某种线程本地查找的开销。

errno的地址作为一个线程中的常量,也提供了向后兼容使用errno.h包含文件的旧的源代码,因为它们应该已经完成​​了(参见这个来自linux的errno手册页 ,明确警告不要使用extern int errno;如过去常见的那样)。

我读标准的方法是允许这种线程本地存储,同时保持类似于旧的extern int errno;的语义和语法extern int errno; 当使用errno并允许旧的用法以及某种不支持multithreading的embedded式设备的交叉编译器时。 但是,由于使用macros定义,语法可能相似,所以不应该使用旧式捷径声明,因为该声明不是实际的errno

我们可以find一个反例:因为一个位域可以有inttypes, errno可以是一个位域。 在这种情况下, &errno将是无效的。 标准的行为在这里没有明确说你可以写&errno ,所以这里定义的未定义的行为就适用了。

C11(n1570),§4。一致性
未定义的行为在本标准中用“未定义的行为”或者省略任何明确的行为定义来表示。

这似乎是一个有效的实现&errno将违反约束:

 struct __errno_struct { signed int __val:12; } *__errno_location(void); #define errno (__errno_location()->__val) 

所以我觉得答案可能不是…