我该如何在C语言中打印errno的符号名称?

15
我可以使用 perror() 或者 strerror() 打印与 errno 相关的“可读性强”的错误消息,但是如果我还想打印 errno 的符号名称(例如,“EAGAIN”),有什么方便的函数或宏可以实现吗?
未来编辑:您最好使用已接受答案中引用的库,而不是下面回答和评论中提出的一些 hacky 代码。
#include <ctype.h>
#include <errno.h>
#include <stdio.h>

int get_errno_name(char *buf, int buf_size) {
    // Using the linux-only gawk instead of awk, because of the convenient
    // match() functionality. For Posix portability, use a different recipe...
    char cmd[] = "e=       && " // errno to be inserted here (max digits = 6)
                 "echo '#include <errno.h>' | "
                 "gcc -dM -E - | " // optionally, use $CC inead of gcc
                 "gawk \"match(\\$0, /^#[[:space:]]*define[[:space:]]+"
                     "(E[[:alnum:]]+)[[:space:]]+$e($|[^[:alnum:]])/, m) "
                     "{ print m[1] }\"";
    {
        // Insert the errno as the "e" shell variable in the command above.
        int errno_digit_c = snprintf(cmd + 2, 6, "%d", errno);
        if (errno_digit_c < 1) {
            fprintf(stderr, "Failed to stringify an errno "
                            "in get_errno_name().\n");
            return -1;
        }
        // Replace the inserted terminating '\0' with whitespace
        cmd[errno_digit_c + 2] = ' ';
    }
    FILE *f = popen(cmd, "r");
    if (f == NULL) {
        perror("Failed to popen() in get_errno_name()");
        return -1;
    }
    int read_size = 0, c;
    while ((c = getc(f)) != EOF) {
        if (isalnum(c)) {
            buf[read_size++] = c;
            if (read_size + 1 > buf_size) {
                fprintf(stderr, "Read errno name is larger than the character "
                                "buffer supplied to get_errno_name().\n");
                return -1;
            }
        }
    }
    buf[read_size++] = '\0';
    if (pclose(f) == -1) {
        perror("Failed to pclose() in get_errno_name()");
        return -1;
    }
    return read_size;
}

有趣的代码。我不会在生产环境中运行一个C编译器的函数 - 它既慢又“不可靠”,因为有些人在运行时没有编译器。假定错误消息集通常不会改变,除非操作系统升级,甚至那样的情况下,向后兼容性意味着大多数错误号码保持不变,缓存结果的机会很多。然而,您可以使用您的代码收集所有系统错误的数据,然后在从编译时数据驱动的函数中使用该输出。 - Jonathan Leffler
@JonathanLeffler 谢谢您的反馈。两点都同意。我可能应该提到以上代码绝对不能这样部署(除非您不介意在gcc和gawk上具有运行时依赖项...)。而且,像这样的代码最好作为编译时(c)make脚本的一部分运行。尽管我的项目往往比较小,所以我通常不会费心去写这样的脚本;这就是为什么以上代码最适合我的个人调试构建。 - Will
7个回答

7

我最近写了errnoname来完成这个任务。

不幸的是,仍然没有标准的API可以完成这个任务。(GNU的glibc有strerrorname_np,但目前没有其他C库提供它,因此它甚至在所有Linux发行版上都不可用,更别说其他地方了。)

我尽可能地使其在所有系统上具有可移植性,同时涵盖该系统上所有 errno名称。到目前为止,包括Linux、Darwin(macOS、iOS等)、BSD(FreeBSD、NetBSD、OpenBSD等)、几个闭源Unix、Windows和其他一些系统。

唯一的“难点”是获取errno名称和值列表以进行查找,知道目标系统上定义了哪些errno,并详尽地处理所有情况,其中多个errno名称可能映射到同一个数字。(例如,在某些系统上,EWOULDBLOCK等于EAGAIN。)

在我的errnoname库中,您不必处理任何问题。我将许多操作系统的errno名称收集到一个列表中作为单独的步骤,然后从中生成实际发布的C代码。 C代码对每个名称使用#ifdef,因此它可以在任何系统上编译,而不管该系统具有哪些errno名称和值。编译结果只是简单高效的代码,不必调用任何程序或在库头中搜索errno
我还提供了两种选项来编译它,一种是数组查找,另一种是switch,因为errno值不能保证接近连续,但在许多系统上是足够连续的,以使数组查找更好。(虽然好的现代编译器可以自动注意到何时最好将开关转换为数组查找。)
无论如何,由于它是根据“零条款BSD许可证”(0BSD)发布的,这是一种宽松的许可证,更准确地说是公共领域等效许可证,因此您可以随心所欲地使用它。

以下是我库中一个旧的复制粘贴函数,以便即使我的存储库链接失效,这个答案也有用,但请注意:

  1. 此处的复制仅涵盖截至2019年8月初我能找到的所有errno名称 - 自那时以来,我已经发现了许多其他名称。

  2. 我将继续更新库以包括我发现的errno名称和系统添加它们的名称,添加其他改进,并添加重复保护,因为可能会发现重复。 但我不会(并且最后一次检查时无法)更新此答案的副本,因为答案大小受限制。

#include <errno.h>

char const * errnoname(int errno_)
{
    switch(errno_)
    {
#ifdef E2BIG
        case E2BIG: return "E2BIG";
#endif
#ifdef EACCES
        case EACCES: return "EACCES";
#endif
#ifdef EADDRINUSE
        case EADDRINUSE: return "EADDRINUSE";
#endif
#ifdef EADDRNOTAVAIL
        case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
#endif
#ifdef EADI
        case EADI: return "EADI";
#endif
#ifdef EADV
        case EADV: return "EADV";
#endif
#ifdef EAFNOSUPPORT
        case EAFNOSUPPORT: return "EAFNOSUPPORT";
#endif
#ifdef EAGAIN
        case EAGAIN: return "EAGAIN";
#endif
#ifdef EAIO
        case EAIO: return "EAIO";
#endif
#ifdef EALIGN
        case EALIGN: return "EALIGN";
#endif
#ifdef EALREADY
        case EALREADY: return "EALREADY";
#endif
#ifdef EASYNC
        case EASYNC: return "EASYNC";
#endif
#ifdef EAUTH
        case EAUTH: return "EAUTH";
#endif
#ifdef EBADARCH
        case EBADARCH: return "EBADARCH";
#endif
#ifdef EBADE
        case EBADE: return "EBADE";
#endif
#ifdef EBADEXEC
        case EBADEXEC: return "EBADEXEC";
#endif
#ifdef EBADF
        case EBADF: return "EBADF";
#endif
#ifdef EBADFD
        case EBADFD: return "EBADFD";
#endif
#ifdef EBADMACHO
        case EBADMACHO: return "EBADMACHO";
#endif
#ifdef EBADMSG
        case EBADMSG: return "EBADMSG";
#endif
#ifdef EBADR
        case EBADR: return "EBADR";
#endif
#ifdef EBADRPC
        case EBADRPC: return "EBADRPC";
#endif
#ifdef EBADRQC
        case EBADRQC: return "EBADRQC";
#endif
#ifdef EBADSLT
        case EBADSLT: return "EBADSLT";
#endif
#ifdef EBADVER
        case EBADVER: return "EBADVER";
#endif
#ifdef EBFONT
        case EBFONT: return "EBFONT";
#endif
#ifdef EBUSY
        case EBUSY: return "EBUSY";
#endif
#ifdef ECANCELED
        case ECANCELED: return "ECANCELED";
#endif
#if defined(ECANCELLED) && (!defined(ECANCELED) || ECANCELLED != ECANCELED)
        case ECANCELLED: return "ECANCELLED";
#endif
#ifdef ECAPMODE
        case ECAPMODE: return "ECAPMODE";
#endif
#ifdef ECHILD
        case ECHILD: return "ECHILD";
#endif
#ifdef ECHRNG
        case ECHRNG: return "ECHRNG";
#endif
#ifdef ECKPT
        case ECKPT: return "ECKPT";
#endif
#ifdef ECLONEME
        case ECLONEME: return "ECLONEME";
#endif
#ifdef ECOMM
        case ECOMM: return "ECOMM";
#endif
#ifdef ECONFIG
        case ECONFIG: return "ECONFIG";
#endif
#ifdef ECONNABORTED
        case ECONNABORTED: return "ECONNABORTED";
#endif
#ifdef ECONNREFUSED
        case ECONNREFUSED: return "ECONNREFUSED";
#endif
#ifdef ECONNRESET
        case ECONNRESET: return "ECONNRESET";
#endif
#ifdef ECORRUPT
        case ECORRUPT: return "ECORRUPT";
#endif
#ifdef ECVCERORR
        case ECVCERORR: return "ECVCERORR";
#endif
#ifdef ECVPERORR
        case ECVPERORR: return "ECVPERORR";
#endif
#ifdef EDEADLK
        case EDEADLK: return "EDEADLK";
#endif
#if defined(EDEADLOCK) && (!defined(EDEADLK) || EDEADLOCK != EDEADLK)
        case EDEADLOCK: return "EDEADLOCK";
#endif
#ifdef EDESTADDREQ
        case EDESTADDREQ: return "EDESTADDREQ";
#endif
#ifdef EDESTADDRREQ
        case EDESTADDRREQ: return "EDESTADDRREQ";
#endif
#ifdef EDEVERR
        case EDEVERR: return "EDEVERR";
#endif
#ifdef EDIRIOCTL
        case EDIRIOCTL: return "EDIRIOCTL";
#endif
#ifdef EDIRTY
        case EDIRTY: return "EDIRTY";
#endif
#ifdef EDIST
        case EDIST: return "EDIST";
#endif
#ifdef EDOM
        case EDOM: return "EDOM";
#endif
#ifdef EDOOFUS
        case EDOOFUS: return "EDOOFUS";
#endif
#ifdef EDOTDOT
        case EDOTDOT: return "EDOTDOT";
#endif
#ifdef EDQUOT
        case EDQUOT: return "EDQUOT";
#endif
#ifdef EDUPFD
        case EDUPFD: return "EDUPFD";
#endif
#ifdef EDUPPKG
        case EDUPPKG: return "EDUPPKG";
#endif
#ifdef EEXIST
        case EEXIST: return "EEXIST";
#endif
#ifdef EFAIL
        case EFAIL: return "EFAIL";
#endif
#ifdef EFAULT
        case EFAULT: return "EFAULT";
#endif
#ifdef EFBIG
        case EFBIG: return "EFBIG";
#endif
#ifdef EFORMAT
        case EFORMAT: return "EFORMAT";
#endif
#ifdef EFSCORRUPTED
        case EFSCORRUPTED: return "EFSCORRUPTED";
#endif
#ifdef EFTYPE
        case EFTYPE: return "EFTYPE";
#endif
#ifdef EHOSTDOWN
        case EHOSTDOWN: return "EHOSTDOWN";
#endif
#ifdef EHOSTUNREACH
        case EHOSTUNREACH: return "EHOSTUNREACH";
#endif
#ifdef EHWPOISON
        case EHWPOISON: return "EHWPOISON";
#endif
#ifdef EIDRM
        case EIDRM: return "EIDRM";
#endif
#ifdef EILSEQ
        case EILSEQ: return "EILSEQ";
#endif
#ifdef EINIT
        case EINIT: return "EINIT";
#endif
#ifdef EINPROG
        case EINPROG: return "EINPROG";
#endif
#ifdef EINPROGRESS
        case EINPROGRESS: return "EINPROGRESS";
#endif
#ifdef EINTEGRITY
        case EINTEGRITY: return "EINTEGRITY";
#endif
#ifdef EINTR
        case EINTR: return "EINTR";
#endif
#ifdef EINVAL
        case EINVAL: return "EINVAL";
#endif
#ifdef EIO
        case EIO: return "EIO";
#endif
#ifdef EIPSEC
        case EIPSEC: return "EIPSEC";
#endif
#ifdef EISCONN
        case EISCONN: return "EISCONN";
#endif
#ifdef EISDIR
        case EISDIR: return "EISDIR";
#endif
#ifdef EISNAM
        case EISNAM: return "EISNAM";
#endif
#ifdef EJUSTRETURN
        case EJUSTRETURN: return "EJUSTRETURN";
#endif
#ifdef EKEEPLOOKING
        case EKEEPLOOKING: return "EKEEPLOOKING";
#endif
#ifdef EKEYEXPIRED
        case EKEYEXPIRED: return "EKEYEXPIRED";
#endif
#ifdef EKEYREJECTED
        case EKEYREJECTED: return "EKEYREJECTED";
#endif
#ifdef EKEYREVOKED
        case EKEYREVOKED: return "EKEYREVOKED";
#endif
#ifdef EL2HLT
        case EL2HLT: return "EL2HLT";
#endif
#ifdef EL2NSYNC
        case EL2NSYNC: return "EL2NSYNC";
#endif
#ifdef EL3HLT
        case EL3HLT: return "EL3HLT";
#endif
#ifdef EL3RST
        case EL3RST: return "EL3RST";
#endif
#ifdef ELIBACC
        case ELIBACC: return "ELIBACC";
#endif
#ifdef ELIBBAD
        case ELIBBAD: return "ELIBBAD";
#endif
#ifdef ELIBEXEC
        case ELIBEXEC: return "ELIBEXEC";
#endif
#ifdef ELIBMAX
        case ELIBMAX: return "ELIBMAX";
#endif
#ifdef ELIBSCN
        case ELIBSCN: return "ELIBSCN";
#endif
#ifdef ELNRNG
        case ELNRNG: return "ELNRNG";
#endif
#ifdef ELOCKUNMAPPED
        case ELOCKUNMAPPED: return "ELOCKUNMAPPED";
#endif
#ifdef ELOOP
        case ELOOP: return "ELOOP";
#endif
#ifdef EMEDIA
        case EMEDIA: return "EMEDIA";
#endif
#ifdef EMEDIUMTYPE
        case EMEDIUMTYPE: return "EMEDIUMTYPE";
#endif
#ifdef EMFILE
        case EMFILE: return "EMFILE";
#endif
#ifdef EMLINK
        case EMLINK: return "EMLINK";
#endif
#ifdef EMOUNTEXIT
        case EMOUNTEXIT: return "EMOUNTEXIT";
#endif
#ifdef EMOVEFD
        case EMOVEFD: return "EMOVEFD";
#endif
#ifdef EMSGSIZE
        case EMSGSIZE: return "EMSGSIZE";
#endif
#ifdef EMTIMERS
        case EMTIMERS: return "EMTIMERS";
#endif
#ifdef EMULTIHOP
        case EMULTIHOP: return "EMULTIHOP";
#endif
#ifdef ENAMETOOLONG
        case ENAMETOOLONG: return "ENAMETOOLONG";
#endif
#ifdef ENAVAIL
        case ENAVAIL: return "ENAVAIL";
#endif
#ifdef ENEEDAUTH
        case ENEEDAUTH: return "ENEEDAUTH";
#endif
#ifdef ENETDOWN
        case ENETDOWN: return "ENETDOWN";
#endif
#ifdef ENETRESET
        case ENETRESET: return "ENETRESET";
#endif
#ifdef ENETUNREACH
        case ENETUNREACH: return "ENETUNREACH";
#endif
#ifdef ENFILE
        case ENFILE: return "ENFILE";
#endif
#ifdef ENFSREMOTE
        case ENFSREMOTE: return "ENFSREMOTE";
#endif
#ifdef ENOANO
        case ENOANO: return "ENOANO";
#endif
#ifdef ENOATTR
        case ENOATTR: return "ENOATTR";
#endif
#ifdef ENOBUFS
        case ENOBUFS: return "ENOBUFS";
#endif
#ifdef ENOCONNECT
        case ENOCONNECT: return "ENOCONNECT";
#endif
#ifdef ENOCSI
        case ENOCSI: return "ENOCSI";
#endif
#ifdef ENODATA
        case ENODATA: return "ENODATA";
#endif
#ifdef ENODEV
        case ENODEV: return "ENODEV";
#endif
#ifdef ENOENT
        case ENOENT: return "ENOENT";
#endif
#ifdef ENOEXEC
        case ENOEXEC: return "ENOEXEC";
#endif
#ifdef ENOIOCTL
        case ENOIOCTL: return "ENOIOCTL";
#endif
#ifdef ENOKEY
        case ENOKEY: return "ENOKEY";
#endif
#ifdef ENOLCK
        case ENOLCK: return "ENOLCK";
#endif
#ifdef ENOLINK
        case ENOLINK: return "ENOLINK";
#endif
#ifdef ENOLOAD
        case ENOLOAD: return "ENOLOAD";
#endif
#ifdef ENOMATCH
        case ENOMATCH: return "ENOMATCH";
#endif
#ifdef ENOMEDIUM
        case ENOMEDIUM: return "ENOMEDIUM";
#endif
#ifdef ENOMEM
        case ENOMEM: return "ENOMEM";
#endif
#ifdef ENOMSG
        case ENOMSG: return "ENOMSG";
#endif
#ifdef ENONET
        case ENONET: return "ENONET";
#endif
#ifdef ENOPKG
        case ENOPKG: return "ENOPKG";
#endif
#ifdef ENOPOLICY
        case ENOPOLICY: return "ENOPOLICY";
#endif
#ifdef ENOPROTOOPT
        case ENOPROTOOPT: return "ENOPROTOOPT";
#endif
#ifdef ENOREG
        case ENOREG: return "ENOREG";
#endif
#ifdef ENOSPC
        case ENOSPC: return "ENOSPC";
#endif
#ifdef ENOSR
        case ENOSR: return "ENOSR";
#endif
#ifdef ENOSTR
        case ENOSTR: return "ENOSTR";
#endif
#ifdef ENOSYM
        case ENOSYM: return "ENOSYM";
#endif
#ifdef ENOSYS
        case ENOSYS: return "ENOSYS";
#endif
#ifdef ENOTACTIVE
        case ENOTACTIVE: return "ENOTACTIVE";
#endif
#ifdef ENOTBLK
        case ENOTBLK: return "ENOTBLK";
#endif
#ifdef ENOTCAPABLE
        case ENOTCAPABLE: return "ENOTCAPABLE";
#endif
#ifdef ENOTCONN
        case ENOTCONN: return "ENOTCONN";
#endif
#ifdef ENOTDIR
        case ENOTDIR: return "ENOTDIR";
#endif
#ifdef ENOTEMPTY
        case ENOTEMPTY: return "ENOTEMPTY";
#endif
#ifdef ENOTNAM
        case ENOTNAM: return "ENOTNAM";
#endif
#ifdef ENOTREADY
        case ENOTREADY: return "ENOTREADY";
#endif
#ifdef ENOTRECOVERABLE
        case ENOTRECOVERABLE: return "ENOTRECOVERABLE";
#endif
#ifdef ENOTRUST
        case ENOTRUST: return "ENOTRUST";
#endif
#ifdef ENOTSOCK
        case ENOTSOCK: return "ENOTSOCK";
#endif
#ifdef ENOTSUP
        case ENOTSUP: return "ENOTSUP";
#endif
#ifdef ENOTTY
        case ENOTTY: return "ENOTTY";
#endif
#ifdef ENOTUNIQ
        case ENOTUNIQ: return "ENOTUNIQ";
#endif
#ifdef ENOUNLD
        case ENOUNLD: return "ENOUNLD";
#endif
#ifdef ENOUNREG
        case ENOUNREG: return "ENOUNREG";
#endif
#ifdef ENXIO
        case ENXIO: return "ENXIO";
#endif
#ifdef EOPCOMPLETE
        case EOPCOMPLETE: return "EOPCOMPLETE";
#endif
#if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || EOPNOTSUPP != ENOTSUP)
        case EOPNOTSUPP: return "EOPNOTSUPP";
#endif
#ifdef EOVERFLOW
        case EOVERFLOW: return "EOVERFLOW";
#endif
#ifdef EOWNERDEAD
        case EOWNERDEAD: return "EOWNERDEAD";
#endif
#ifdef EPASSTHROUGH
        case EPASSTHROUGH: return "EPASSTHROUGH";
#endif
#ifdef EPATHREMOTE
        case EPATHREMOTE: return "EPATHREMOTE";
#endif
#ifdef EPERM
        case EPERM: return "EPERM";
#endif
#ifdef EPFNOSUPPORT
        case EPFNOSUPPORT: return "EPFNOSUPPORT";
#endif
#ifdef EPIPE
        case EPIPE: return "EPIPE";
#endif
#ifdef EPOWERF
        case EPOWERF: return "EPOWERF";
#endif
#ifdef EPROCLIM
        case EPROCLIM: return "EPROCLIM";
#endif
#ifdef EPROCUNAVAIL
        case EPROCUNAVAIL: return "EPROCUNAVAIL";
#endif
#ifdef EPROGMISMATCH
        case EPROGMISMATCH: return "EPROGMISMATCH";
#endif
#ifdef EPROGUNAVAIL
        case EPROGUNAVAIL: return "EPROGUNAVAIL";
#endif
#ifdef EPROTO
        case EPROTO: return "EPROTO";
#endif
#ifdef EPROTONOSUPPORT
        case EPROTONOSUPPORT: return "EPROTONOSUPPORT";
#endif
#ifdef EPROTOTYPE
        case EPROTOTYPE: return "EPROTOTYPE";
#endif
#ifdef EPWROFF
        case EPWROFF: return "EPWROFF";
#endif
#ifdef EQFULL
        case EQFULL: return "EQFULL";
#endif
#ifdef EQSUSPENDED
        case EQSUSPENDED: return "EQSUSPENDED";
#endif
#ifdef ERANGE
        case ERANGE: return "ERANGE";
#endif
#ifdef ERECYCLE
        case ERECYCLE: return "ERECYCLE";
#endif
#ifdef EREDRIVEOPEN
        case EREDRIVEOPEN: return "EREDRIVEOPEN";
#endif
#ifdef EREFUSED
        case EREFUSED: return "EREFUSED";
#endif
#ifdef ERELOC
        case ERELOC: return "ERELOC";
#endif
#ifdef ERELOCATED
        case ERELOCATED: return "ERELOCATED";
#endif
#ifdef ERELOOKUP
        case ERELOOKUP: return "ERELOOKUP";
#endif
#ifdef EREMCHG
        case EREMCHG: return "EREMCHG";
#endif
#ifdef EREMDEV
        case EREMDEV: return "EREMDEV";
#endif
#ifdef EREMOTE
        case EREMOTE: return "EREMOTE";
#endif
#ifdef EREMOTEIO
        case EREMOTEIO: return "EREMOTEIO";
#endif
#ifdef EREMOTERELEASE
        case EREMOTERELEASE: return "EREMOTERELEASE";
#endif
#ifdef ERESTART
        case ERESTART: return "ERESTART";
#endif
#ifdef ERFKILL
        case ERFKILL: return "ERFKILL";
#endif
#ifdef EROFS
        case EROFS: return "EROFS";
#endif
#ifdef ERPCMISMATCH
        case ERPCMISMATCH: return "ERPCMISMATCH";
#endif
#ifdef ESAD
        case ESAD: return "ESAD";
#endif
#ifdef ESHLIBVERS
        case ESHLIBVERS: return "ESHLIBVERS";
#endif
#ifdef ESHUTDOWN
        case ESHUTDOWN: return "ESHUTDOWN";
#endif
#ifdef ESOCKTNOSUPPORT
        case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
#endif
#ifdef ESOFT
        case ESOFT: return "ESOFT";
#endif
#ifdef ESPIPE
        case ESPIPE: return "ESPIPE";
#endif
#ifdef ESRCH
        case ESRCH: return "ESRCH";
#endif
#ifdef ESRMNT
        case ESRMNT: return "ESRMNT";
#endif
#ifdef ESTALE
        case ESTALE: return "ESTALE";
#endif
#ifdef ESTART
        case ESTART: return "ESTART";
#endif
#ifdef ESTRPIPE
        case ESTRPIPE: return "ESTRPIPE";
#endif
#ifdef ESYSERROR
        case ESYSERROR: return "ESYSERROR";
#endif
#ifdef ETIME
        case ETIME: return "ETIME";
#endif
#ifdef ETIMEDOUT
        case ETIMEDOUT: return "ETIMEDOUT";
#endif
#ifdef ETOOMANYREFS
        case ETOOMANYREFS: return "ETOOMANYREFS";
#endif
#ifdef ETXTBSY
        case ETXTBSY: return "ETXTBSY";
#endif
#ifdef EUCLEAN
        case EUCLEAN: return "EUCLEAN";
#endif
#ifdef EUNATCH
        case EUNATCH: return "EUNATCH";
#endif
#ifdef EUSERS
        case EUSERS: return "EUSERS";
#endif
#ifdef EVERSION
        case EVERSION: return "EVERSION";
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EWOULDBLOCK != EAGAIN)
        case EWOULDBLOCK: return "EWOULDBLOCK";
#endif
#ifdef EWRONGFS
        case EWRONGFS: return "EWRONGFS";
#endif
#ifdef EWRPROTECT
        case EWRPROTECT: return "EWRPROTECT";
#endif
#ifdef EXDEV
        case EXDEV: return "EXDEV";
#endif
#ifdef EXFULL
        case EXFULL: return "EXFULL";
#endif
    }
    return 0;
}

你本可以使用宏来避免重复标识符。 - user21508463
@YvesDaoust 代码是从一个errno名称列表生成的(链接:https://github.com/mentalisttraceur/errnoname/blob/90b4164bb98046a2a12ad572f1b4ba008db57b20/for-maintainers/generate-c.sh)。宏不能包含`#ifdef`和`#if`指令,所以最好的情况是`#define X(name) case name: return #name;(对于可选的源级数组替代开关优化,可以使用#define X(name) [name] = #name,`)。将部分扩展推迟到C预处理器中并没有太大价值,因为其余的扩展已经在代码生成脚本中进行了。 - mtraceur

7

glibc 提供了一个函数 (GNU 扩展):strerrorname_np()

它在 <string.h> 中声明。

对于有效值,它返回 errno 名称,对于无效的值,它返回 NULL

man strerrorname_np

它被添加在 glibc 2.32 中。


1
虽然是小事,但令人沮丧的是,strerrorname_np没有明确说明如果传入的错误号为0会发生什么:它会返回NULL,还是将其视为有效数字并返回类似于“EOK”的内容?从文档中无法确定,必须查看源代码(这种挫败感就是我确保有意识地选择并明确记录errnoname其中一个选项的原因)。但这比任何其他libc所做的都更接近标准解决方案。(并且要明确的是,我是这个答案的投票者之一。) - mtraceur
1
实验性地,它返回字符串"0"。我将在手册页面中记录这个细节。以下代码errno = 0; printf("<%m> <%#m> <%s> <%s>\n", strerror(0), strerrorname_np(0));打印出<Success> <0> <Success> <0>。我记得这个问题已经讨论过了,并且决定不返回NULL以避免触发Undefined行为,因为这通常是用于打印输出的。"0"似乎是最合理的做法。 - alx - recommends codidact
1
@mtraceur 对不起,我拖了这么久,我忘了!我终于记录下了strerrorname_cp(0)的结果。 - undefined
@mtraceur https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/?id=019aad50584289476a9f206adf074326e106713d - undefined

4

这并不是一项简单的任务。

你可以创建一个程序(我已经创建了一个,可以封装为库函数),将数字转换为名称。但生成表格有一定难度。我使用一个Perl脚本,运行编译器(GCC或等效编译器)并添加选项(-H)以列出包含在/usr/include/errno.h中的头文件,然后扫描这些文件,查找名称(#define加上大写字母或数字的E)、数字和注释。这在Linux、Mac OS X、Solaris、HP-UX和AIX上都可以运行。但这并不特别简单。

请注意,Mac OS X上的errno.h包括一个名为ELAST的名称(这个名称被保留,供实现使用),它是最高数字的重复项(但映射会因发布版本而改变;在Mountain Lion中,它是105,在Mavericks中则是106)。


1
echo '#include <errno.h>' | $CC -dM -E - | grep -E '^#[[:space:]]*define[[:space:]]+E' - R.. GitHub STOP HELPING ICE
@R..:大体上是的,但如果您想要有用地编译它,就必须将符号名称(副本)转换为字符串,并可能捕获注释(尽管您可以使用strerror()来处理错误消息部分),等等。 - Jonathan Leffler
我们可以对可移植性进行争论(-dM-将与gcc一起工作,-H也可能与clangicc一起工作,但不一定适用于其他编译器)。输出略有不同,但净结果基本相同。我认为我们实际上是非常一致的。Perl读取头文件的一个优点是它不必关注#ifdef,并且可以获取您提供的定义可能会被忽略的定义。这并不是明确的。某些平台可能会重新组织信息的布局并搞砸一切。无论如何完成都存在故障。 - Jonathan Leffler
@Duck 完全正确。如果只有 strerror()/perror() 在输出结尾处包含 errno 及其名称的括号,那么在我看来每个人都会更好。 - Will
好的,我们的目标不同。我所拥有的对我来说有效,你可以用自己的方式做事。 - Jonathan Leffler
显示剩余4条评论

2
据我所知,你不能这样做。一些整数错误常量被映射到多个符号名称,例如EAGAIN和EWOULDBLOCK。显然,您可以在设置errno的命令的man页面上查找它们。

1
EAGAIN和EWOULDBLOCK是唯二可能相同的值。所有其他值必须具有不同的值。 - Dan Moulding
1
在Linux上,EDEADLK和EDEADLOCK也表示死锁。 - Duck
POSIX <errno.h> 列出了 EAGAIN 和 EWOULDBLOCK 作为一对,但也包括 ENOTSUP 和 EOPNOTSUPP。平台可能有其他重复的名称。由于给定的数字只需要一个名称,您可以选择一个作为规范名称。 - Jonathan Leffler
@Duck EDEADLK和DEADLOCK意思相同...(DEADLK是DEADLOCK的缩写形式) - Perceval

1
如果您知道预期的错误,可以按照以下方式编写大型 switch/caseif/else 代码块来处理 errno
if (errno == EAGAIN)
    fprintf(stderr, "EAGAIN");

这个明显的问题在于,如果你想要特定的errno“名称”,你需要针对每个可能的选项进行编写,而这有相当多的选项。

这不是很通用。至少创建一个使用字符串数组的函数,并返回与数字对应的名称的char const * - Jonathan Leffler

0

我不知道是否有一个函数可以做到这一点,但如果您熟悉程序返回的错误,编写一个函数并不难。

您可以编写包含strerror()输出的字符串,并使用for循环和if语句以及strcmp()(根据成功或失败返回值)来比较您编写的字符串。如果它们相同,您可以输出符号名称*,如果将其设置为另一个字符串。


你为什么需要对strerror的输出使用strcmp函数? - Leigh
你可以在errno.h中查找它们,并基于此编写一些内容。这将有效...直到某些东西发生了改变。 - Duck

0

由于符号名称存储为枚举并且C将它们视为Ints。您需要创建一个类似于此How to convert enum names to string in c">SO问题的函数。您可以很容易地将其缩短为宏,但必须为每个错误创建一个案例。


2
C标准(至少C99版本)规定E*常量是宏,而非枚举类型。 - jwodder
好的,GNU编译器会打开它们并将它们视为枚举。对于误导性信息,我感到抱歉。 - Benjamin Trent

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