如何检查errno的值?

25

我正在使用系统调用,如果它失败了,我需要根据不同的错误号(errnos)执行不同的操作。

我需要编写类似于下面的代码:

int res;
res = systemCall();
if (res == -1)
{
    if (errno == ENOMSG)
    {
        doSomething();
    }
    else
    {
        doSomethingElse();
    }
}

perror无法帮助,因为它仅仅打印出值。

至于strerro - 如果我需要它,我不确定如何使用它,因为这里说实际字符串与错误不同。摘自手册:“(例如,如果errnum是EINVAL,则返回的描述将是“Invalid argument”)”。

我正在使用Linux。 系统调用:msgsend和msgrcv (https://linux.die.net/man/2/msgrcv)。我不确定您所问的C库是什么。

我发现我没有很好地解释清楚自己。

语句if(errno == ENOMSG)是否有效?是否有errno变量?基本上我的问题是:在if语句中应该放什么来测试errno?


1
在哪个操作系统上?使用什么确切的系统调用?使用哪个C库?请编辑您的问题以改进它。 - Basile Starynkevitch
1
每个系统调用都有可能的errno值列表(请参阅其“man”页面),这将让您知道应该测试什么。 - Myst
2
你写的很好。记住,没有库函数会将 errno 设置为零,并且除非函数指示错误并记录设置 errno,否则不应该对其进行测试。请注意,许多 pthreads 函数不设置 errno;例如,它们返回错误号。此外,即使成功,函数也可以将 errno 设置为非零值。例如,在 Solaris 上,如果标准输出不是终端,则即使 printf() 调用成功,也可能将 errno 设置为 ENOTTY。 - Jonathan Leffler
3个回答

34
如何检查errno的值:
  1. 你需要包含#include <errno.h>
  2. 是的,你可以像这样说 if(errno == ENOENT) { ... },这是常见且推荐的方法。
  3. 一般情况下,不要使用errno来确定是否发生了错误。要检查函数的返回值,如果返回值表示出现了错误,则检查errno以查看错误原因。 (更多信息请参见下文。)
  4. errno看起来像一个变量,但实际上并不是。只要像这样说if(errno == ENOENT) { ... },就不需要关心这个问题。但你可能不应该尝试做一些像int errno_ptr = &errno;这样的事情。
  5. 你可以使用perror()strerror()等函数,获取与errno值对应的人类可读的错误字符串。但是,是的,你得到的字符串通常是诸如“没有这样的文件或目录”之类的内容。我不知道将ENOENT转换为字符串"ENOENT"的好方法。(补充:有关答案,请参见此问题。)

再谈一下第3点。 有时候很难抵制说些这样的话

errno = 0;
printf("Hello, world!\n");
if(errno != 0) {
    fprintf(stderr, "printf failed!\n");
}

但不要那样做。相反,要做...

int retval = printf("Hello, world!\n");
if(retval < 0) {
    fprintf(stderr, "printf failed!\n");
}

原因是,在执行其工作的过程中,printf 可能会发生导致错误的操作,可能会设置 errno,但是 printf 可能已经从该错误中恢复并成功完成。

有一些库函数保证如果没有出现错误则不会触及 errno(我认为其中一个例子可能是 strtol)��但通常情况下,这是需要小心处理的。

再谈一下第四点。 errno 看起来像一个变量,更具体地说,它看起来像一个全局变量。但全局变量当然是不好的。但是 errno 已经存在了很长时间;有数千万行使用它的代码;它仍然基本上相当方便;要“修复”它已经太晚了。因此,相反,如果你窥探幕后,你会发现大多数实现都做了类似的事情:

extern int *__errno_pointer;
#define errno (*__errno_pointer)
或者
extern int *__errno_pointer_function();
#define errno (*__errno_function())

这样一来,即使在多线程代码中,他们也可以安排errno正常工作。


extern int __errno_pointer; 你是不是想说 extern int *__errno_pointer;? - user16217248
@user16217248 我当然做了。天哪,谢谢你指出来。已经修复了。 - Steve Summit

20

我假设你正在使用Linux,并且我认为你并没有“直接”使用系统调用,而是使用了一些(简单的)包装器(从你的C库中列出),这些包装器在syscalls(2)中列出。请注意,一些奇怪的系统调用未被C库包装(一个众所周知的未被包装的系统调用的例子是sigreturn(2),您可能永远不应该使用它)。通常C库是GNU glibc,但也可能是musl-libc等。还要注意,内核原始系统调用具有不同的调用约定,与普通的C函数不同(因此在实践中需要一个libc包装器,并负责处理errno)。还要注意,errno(3)通常是一个宏(几乎像某个变量)。

msgrcv(2)手册记录了errno可能是E2BIGEACCESEFAULT ... ENOMSGENOSYS...(请参考该手册以获取所有可能错误的列表)。

因此,您可以编写类似以下的代码:

ssize_t siz = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
if (siz<0) { // msgrcv failed and has set errno
  if (errno == ENOMSG) 
    dosomething();
  else if (errno == EAGAIN) 
    dosomethingelse();
  /// etc
  else {  
     syslog(LOG_DAEMON|LOG_ERR, "msgrcv failure with %s\n",
            strerror(errno)); 
     exit(EXIT_FAILURE); 
  };
};

语句if (errno == ENOMSG)是有效的吗?

是的,当某些系统调用失败时(例如siz<0),您希望仅在测试errno后。

是否有名为errno的变量?

不再有了。请仔细阅读 errno(3)文档。您不应该声明extern int errno;(这在20世纪80年代是可能的,但在21世纪已经不可能了),而应该始终使用#include <errno.h>并将errno用作变量, 实际上它几乎总是一些宏(其定义出现在/usr/include/bits/errno.h中,该文件被/usr/include/errno.h包含)。

顺便说一下,SysV样式的接口往往会过时,并且不总是可用。我建议使用POSIX消息队列功能,阅读mq_overview(7)

您可能希望阅读免费下载的 Advanced Linux Programming(一本旧书;您可以购买更好和更新的内容),以及从intro(2)& syscalls(2) & intro(3)可达到的所有手册页面。


高级Linux编程下载链接可能已经失效或过时了。对我来说似乎无法工作。 - Matthew Plemmons
已经更正,一些随机链接今天可以使用。实际上,如果您有能力负担得起的话,最好阅读一本更新的书籍。 - Basile Starynkevitch
谢谢你提醒我!我会去找一些更新的东西。如果你有任何想法,请随时告诉我,我会很高兴听取建议。 - Matthew Plemmons
我提供了一个ALP副本的链接,现在可以下载。你可以重新分发它(但我不是律师)。 - Basile Starynkevitch
强烈推荐阅读《Linux编程接口》(https://man7.org/tlpi/)。同时建议查看warn(3)和err(3)。当出现错误时,通常您只想打印一条有关错误的消息,带有一些上下文信息(例如文件名),并可能退出程序。 - James K. Lowden
@JamesK.Lowden 现在如果它们被 POSIX 标准化就好了... - Haris

7

包含errno.h

一些例子:

// Error codes
#define EPERM        1  /* Operation not permitted */
#define ENOENT       2  /* No such file or directory */
#define ESRCH        3  /* No such process */
#define EINTR        4  /* Interrupted system call */
#define EIO          5  /* I/O error */
#define ENXIO        6  /* No such device or address */
#define E2BIG        7  /* Argument list too long */
#define ENOEXEC      8  /* Exec format error */
#define EBADF        9  /* Bad file number */
#define ECHILD      10  /* No child processes */
#define EAGAIN      11  /* Try again */
#define ENOMEM      12  /* Out of memory */
#define EACCES      13  /* Permission denied */
#define EFAULT      14  /* Bad address */
#define ENOTBLK     15  /* Block device required */
#define EBUSY       16  /* Device or resource busy */
#define EEXIST      17  /* File exists */
#define EXDEV       18  /* Cross-device link */
#define ENODEV      19  /* No such device */
#define ENOTDIR     20  /* Not a directory */
#define EISDIR      21  /* Is a directory */
#define EINVAL      22  /* Invalid argument */
#define ENFILE      23  /* File table overflow */
#define EMFILE      24  /* Too many open files */
#define ENOTTY      25  /* Not a typewriter */
#define ETXTBSY     26  /* Text file busy */
#define EFBIG       27  /* File too large */
#define ENOSPC      28  /* No space left on device */
#define ESPIPE      29  /* Illegal seek */
#define EROFS       30  /* Read-only file system */
#define EMLINK      31  /* Too many links */
#define EPIPE       32  /* Broken pipe */
#define EDOM        33  /* Math argument out of domain of func */
#define ERANGE      34  /* Math result not representable */

你的实现可能有更多的errno包含文件,例如/usr/include/asm-generic/errno.h


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