连续调用`errno`应该避免吗?

8

在处理相同的错误时,多次调用errno是否安全?还是使用本地副本更安全?

下面的示例说明了我的问题:

// If recvfrom() fails it returns -1 and sets errno to indicate the error.
int res = recvfrom(...);
if (res < 0)
{
    // Risky?
    printf("Error code: %d. Error message: %s\n", errno, strerror(errno));

    // Safer alternative?
    int errorNumber = errno;
    printf("Error code: %d. Error message: %s\n", errorNumber, strerror(errorNumber));
}

1
+1 有趣的问题,结果有一个非平凡的答案和可能的实际后果。 - R.. GitHub STOP HELPING ICE
5个回答

6
“errno”的值仅在调用明确声明设置它的函数后才定义,在下一次函数调用更改它或应用程序分配值之前,其值将保持不变。

http://www.opengroup.org/onlinepubs/009695399/functions/errno.html

然而,即使 strerror 理论上也可以被视为能够更改它的函数调用(请参见 schot 的评论),因此理论上仍应该使用先保存再操作的形式。


1
然而,printf可能会导致errno的更改。http://linux.die.net/man/3/errno(我相信它来自Linux)的手册警告不要在printf中使用它。但是对于您的特定情况,这不是问题 - 编译器将在调用printf之前同时评估errno和strerror(errno)。 - Habbie
2
但是调用 strerror 可能会改变 errno。虽然我认为没有任何愚蠢/邪恶的实现会这样做(除了可能是 DeathStation 9000)。 - schot
True,编译器可能会在评估errno之前评估strerror(errno)。我正在更新我的答案。 - Habbie
5
令人难以置信的是,strerror在某些情况下被指定写入errno:http://www.opengroup.org/onlinepubs/9699919799/functions/strerror.html而具有讽刺意味的是,测试`strerror`是否失败的唯一方法是在调用`strerror`之前将`errno`设置为0,因为返回值不反映成功或失败。因此,OP可能有点道理... - R.. GitHub STOP HELPING ICE
2
据我所知,C标准允许任何库函数以完全未指定的方式更新errno,除非该函数明确指定不这样做,因此,如果我们不谈论POSIX,这个问题甚至更加相关。 - R.. GitHub STOP HELPING ICE
@R..:根据您提供的链接,strerror只有在提供的错误号码不是有效的错误号码时才会失败(因此写入errno)。 - JeremyP

2

任何标准库函数,包括printf和strerror,都可以更改errno的值,即使实际上没有发生错误:

7.5 3 在程序启动时,errno的值为零,但是任何库函数都不会将其设置为零。170)无论是否存在错误,库函数调用都可能将errno的值设置为非零值,前提是在此国际标准的函数描述中未记录使用errno。


1
与此有关的问题:您能否在标准中找到任何语言,禁止strtol和其它函数在没有发生溢出时将errno设置为ERANGE?标准程序是在调用这些函数之前将errno设置为0,并在返回LONG_MINLONG_MAX时检查errno - 但是如果它们可以无缘无故地将errno设置为ERANGE,那么这个标准测试似乎就无效了... - R.. GitHub STOP HELPING ICE
如果特定函数的使用errno已经定义,那么除了7.5中明确说明的行为之外,不允许任何其他行为:“只要在函数的描述中没有记录errno的使用”。 - Secure

1

errno是变量而不是函数。当您使用它时,它无法重置。因此,假设您不调用任何可以更改/重置errno的函数,多次使用errno数字是可以的。


1

现在通常情况下,errno不再是一个简单的变量,而是更为复杂的东西:

... errno扩展为一个可修改的lvalue,其类型为int,由几个库函数将其值设置为正错误号。未指定errno是宏还是具有外部链接的标识符。如果抑制宏定义以访问实际对象,或者程序定义了一个名为errno的标识符,则行为是未定义的。

例如,在POSIX中,它保证评估为当前线程特定的内容。因此,它可能具有比简单变量更高的访问成本。

因此,如果性能是一个问题,我会选择使用本地副本,尽管我从未真正进行过测试。


0

我刚刚自己研究了一下,我认为另一个函数可能更适合这个问题,那就是perror。perror非常简单,例如如果你malloc一些内存并且想要类似于strerror提供的有意义的错误消息,以便在malloc失败时使用:

char **str_array = (char**) malloc(SOME_CONSTANT * sizeof(char*));
if (str_array == NULL){
    perror("malloc failed on str_array");
}

perror函数会打印输入的字符串,然后加上一个空格和分号,并打印出可读性强的错误信息。它似乎没有strerror那样的副作用,除非我因为没有ERRORS部分而错误解释man手册:http://man7.org/linux/man-pages/man3/perror.3.html

我也在进行可能失败的连续调用,perror似乎是更少代码、更好的语法选择。但是,由于我是C语言新手,请编辑或删除如果这个信息是不准确的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接