& errno合法C?

时间:2012-10-18 00:35:12

标签: c language-lawyer errno

每7.5,

  

[errno]扩展为具有int类型的可修改的lvalue175,其值由多个库函数设置为正错误号。未指定errno是宏还是使用外部链接声明的标识符。如果为了访问实际对象而禁止宏定义,或者程序定义名为errno的标识符,则行为是未定义的。

     

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

我不清楚这是否足以要求&errno不是约束违规。 C语言有左值(例如寄存器存储类变量;但是这些变量只能是自动的,因此errno无法定义为&运算符违反约束。) p>

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

5 个答案:

答案 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)

我们可以找到一个反例:因为位字段可以具有类型interrno可以是位字段。在这种情况下,&errno将无效。标准的行为在这里并没有明确表示你可以编写&errno,因此未定义行为的定义适用于此。

  

C11(n1570),§4。一致性
  本国际中另有说明了未定义的行为   标准用“未定义的行为”或“遗漏任何行为”   行为的明确定义。

答案 4 :(得分:0)

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

struct __errno_struct {
    signed int __val:12;
} *__errno_location(void);

#define errno (__errno_location()->__val)

所以我认为答案可能不是......