每7.5,
[errno]扩展为具有int类型的可修改的lvalue175,其值由多个库函数设置为正错误号。未指定errno是宏还是使用外部链接声明的标识符。如果为了访问实际对象而禁止宏定义,或者程序定义名为errno的标识符,则行为是未定义的。
175)宏errno不必是对象的标识符。它可能会扩展为函数调用产生的可修改的左值(例如,* errno())。
我不清楚这是否足以要求&errno
不是约束违规。 C语言有左值(例如寄存器存储类变量;但是这些变量只能是自动的,因此errno
无法定义为&
运算符违反约束。) p>
如果&errno
是合法的C,是否需要保持不变?
答案 0 :(得分:17)
所以§6.5.3.2p1指定
一元&的操作数运算符应该是函数指示符,[]或一元*运算符的结果,或者是一个左值,它指定一个不是位字段的对象,并且不用寄存器存储类说明符声明。
我认为可以理解为&lvalue
适用于不属于这两个类别的任何左值。正如您所提到的,errno
不能使用寄存器存储类说明符声明,并且我认为(尽管现在不追逐引用检查)您不能拥有类型为plain {的位域。 {1}} 强>
所以我认为规范要求int
是合法的C.
如果& errno是合法的C,是否需要保持不变?
据我了解,允许&(errno)
成为宏(以及它在例如glibc中的原因)的一部分原因是允许它作为线程局部存储的引用,在这种情况下它肯定不会在线程之间保持不变。我没有理由认为它必须是不变的。只要errno
的值保留了指定的语义,我就认为不正确的C库无法在程序过程中更改errno
以引用不同的内存地址 - 例如每次设置&errno
时,都可以通过释放和重新分配后备存储。
您可以想象维护一个由库设置的最后N个errno值的环形缓冲区,并且errno
总是指向最新的。我不认为它会特别有用,但我看不出它违反规范的方式。
答案 1 :(得分:14)
我很惊讶没有人引用C11 spec。为长报价道歉,但我认为这是相关的。
7.5错误
标题定义了几个宏......
...和
errno
扩展为具有类型
int
和线程本地的可修改左值(201) 存储持续时间,其值设置为正误差数 几个库函数。如果一个 禁止宏定义以访问实际对象,或 程序定义名称为errno
的标识符,行为为 未定义。初始线程中
errno
的值为零 程序启动(其他线程中errno
的初始值是 不确定值),但永远不会被任何库设置为零 函数。(202)errno的值可以由库设置为非零 函数调用是否有错误,提供使用errno
未记录在此函数的描述中 国际标准。(201)宏
errno
不必是对象的标识符。它可能扩展到a 由函数调用产生的可修改左值(例如,*errno()
)。(202)因此,使用
errno
进行错误检查的程序应该在a之前将其设置为零 库函数调用,然后在后续库函数调用之前检查它。的 当然,库函数可以在输入时保存errno
的值,然后将其设置为零, 只要原始值恢复,如果errno
的值在...之前仍然为零 返回。
"线程本地"意味着register
已经出局。类型int
表示位域已用完(IMO)。所以&errno
看起来合法。
持续使用像"它"和" 值"表明该标准的作者没有考虑&errno
是非常数的。我想人们可以想象&errno
在特定线程中不是常量的实现,但是要按照脚注所说的方式使用(设置为零,然后在调用库函数后检查),它必须是故意的对抗性,并且可能需要专门的编译器支持才能具有对抗性。
简而言之,如果规范确实允许非常数&errno
,我认为这不是故意的。
[更新]
R上。在评论中提出了一个很好的问题。在考虑之后,我相信我现在知道他的问题和原始问题的正确答案。亲爱的读者,让我看看能不能说服你。
R上。指出GCC在顶层允许这样的事情:
register int errno asm ("r37"); // line R
这会将errno
声明为注册r37
中的全局值。显然,它将是一个线程局部可修改的左值。那么,符合标准的C实现是否可以像这样声明errno
?
答案是否。当您或我使用"声明"时,我们通常会考虑口语和直观的概念。但是标准并不是口语或直觉;它说正好,它的目的只是使用明确定义的术语。在"声明"的情况下,标准本身定义了术语;当它使用该术语时,它使用自己的定义。
通过阅读规范,您可以准确地了解"声明"确切地说它是不是。换句话说,该标准描述了语言" C"。它没有描述"某些语言不是C"。就标准而言," C与扩展"只是"某些语言不是C"。
因此,从标准的角度来看,R 行根本不是声明。它甚至没有解析!不妨阅读:
long long long __Foo_e!r!r!n!o()blurfl??/**
就规范而言,这同样是一个"声明"如R行;即,根本不是。
因此,当C11规范说,在6.5.3.2节中:
一元
&
运算符的操作数应该是一个函数 指示符,[]
或一元*
运算符的结果,或者是左值 指定一个不是bit-fi字段且未声明的对象 寄存器存储类指定器。
...这意味着非常精确的不指的是像Line R这样的东西。
现在,考虑int
引用的errno
对象的声明。 (注意:我并不是指errno
名称的声明,因为如果errno
是一个宏,我当然可能没有这样的声明。我的意思是声明基础int
对象。)
上面的语言说你可以取一个左值的地址,除非它指定一个位字段或者指定一个对象"声明" register
。底层errno
对象的规范表明它是一个可修改的int
左值,具有线程局部持续时间。
现在,规范确实没有说明底层errno
对象必须被声明。也许它只是通过一些实现定义的编译器魔术出现。但同样,当规范声明"用寄存器存储类说明符"声明时,它使用自己的术语。
因此,基础errno
对象是"声明"在标准意义上,在这种情况下,它不能同时是register
和线程本地的;或者它未被声明 ,在这种情况下它不会被声明为register
。无论哪种方式,因为它是一个左值,你可以拿它的地址。
(除非它是一个位字段,但我认为我们同意位字段不是int
类型的对象。)
答案 2 :(得分:3)
errno
的原始实现是一个全局int变量,如果遇到错误,各种标准C库组件用于指示错误值。然而,即使在那些日子里,我必须要小心reentrant code或者在处理错误时可以将errno
设置为不同的值的库函数调用。通常,如果由于某些其他函数或代码片段明确地或通过库函数调用设置errno
的值而需要任何长度的错误代码,则可以将值保存在临时变量中
因此,使用全局int的这个原始实现,使用运算符的地址并根据地址保持不变几乎内置于库的结构中。
然而,对于多线程,不再存在单个全局,因为具有单个全局不是线程安全的。所以让thread local storage使用一个返回指向已分配区域的指针的函数的想法。所以你可能会看到一个类似于以下完全虚构的例子的构造:
#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;
用作单个全局而不是线程安全的int变量,或者像if (errno == 22) { // handle the error
那样检查它,甚至像int *pErrno = &errno;
之类的东西。这一切都有效,因为最后线程本地数据区域被分配并保持放置并且没有移动,使errno
看起来像extern int
的宏定义隐藏了其实际实现的管道。 / p>
我们不想要的一件事是在我们访问该值时,errno
的地址突然在线程的时间片与某种动态分配,克隆,删除序列之间转换。当你的时间片结束时,它会启动,除非你有某种同步或者某种方式在你的时间片到期后保持CPU,让线程局部区域移动对我来说似乎是一个非常冒险的命题。
这反过来暗示您可以依赖运算符的地址为特定线程提供常量值,尽管常量值在线程之间会有所不同。我可以使用errno
的地址看到库,以减少每次调用库函数时进行某种线程本地查找的开销。
在线程中将errno
的地址保持为常量还提供了与使用errno.h包含文件的旧源代码的向后兼容性(请参阅此man page from linux for errno明确警告不要像过去常见的那样使用extern int errno;
。
我读取标准的方式是允许这种线程本地存储,同时在使用extern int errno;
时保持与旧errno
类似的语义和语法,并允许旧的用法某种不支持多线程的嵌入式设备的交叉编译器。但是,由于使用宏定义,语法可能类似,因此不应使用旧样式快捷方式声明,因为该声明不是实际的errno
真实的。
答案 3 :(得分:0)
我们可以找到一个反例:因为位字段可以具有类型int
,errno
可以是位字段。在这种情况下,&errno
将无效。标准的行为在这里并没有明确表示你可以编写&errno
,因此未定义行为的定义适用于此。
C11(n1570),§4。一致性
本国际中另有说明了未定义的行为 标准用“未定义的行为”或“遗漏任何行为” 行为的明确定义。
答案 4 :(得分:0)
这似乎是&errno
违反约束的有效实现:
struct __errno_struct {
signed int __val:12;
} *__errno_location(void);
#define errno (__errno_location()->__val)
所以我认为答案可能不是......