C库函数、系统函数等调用错误的处理方法

Wesley13
• 阅读 540

几乎所有的系统函数和库函数在执行时都会通过返回特定的值来说明成功或出错。我们在调用它们后,必须马上对其返回值进行检测,如果调用出错则要进行相应的处理(一般是向终端输出错误信息并终止程序运行)。否则在今后程序出错时,如果通过调试去定位到该错误将会花费很长的时间。

当然也有某些系统调用从不失败(例如getpid()或_exit()等),在调用它们时可以不进行错误检测。

1.如何发现系统调用出错

  • 每个系统调用的manual page都会详细介绍可能的返回值。通常,用“-1”来表示出错,因此出错检测语句可以这样写:

    fd = open(pathname, flags, mode); if(fd == -1){

    /* code to handle the error */
    

    } ...

    if(close(fd) == -1){

    /* code to handle the error */
    

    }

**注意:**不同于UNIX系统函数和C库函数,POSIX标准下的函数成功执行时,函数返回“0”,出错时设定errno为特定的非0整数值。

当一个系统调用失败时,内核会根据错误类型将error设定一个特定的非负整数。头文件<error.h>为每一个可能的error值定义了一个以“E”开头的宏名,在错误处理函数中可以通过检测这个特定的宏名来判断错误类型。

cnt = read(fd, buf, numtybes);
if(cnt == -1){
   
    if(errno == EINTR)
        fprintf(stderr, "read was interrupted by a signal\n");
    else{
   
        /*some other error occurred */
    }
}

当系统调用成功时,它并不会将error设定为0。因此,可能出现的一种情况是当前的系统函数成功执行了,但当前error值还是以前某次系统调用出错时设定的值,为此,在对error值进行检测以判定是哪种错误时,一定一定要先检查当前系统调用是否发生错误!

  • 有些系统函数在出错时并非返回“-1”,甚至在执行成功时返回“-1”。这种情况下,我们可以在系统调用前先将error设为0,待系统调用结束后,通过判定error是否为0就可知错误是否发生。

2.如何处理系统调用出错

对待系统调用出错的常见处理方法是基于error值输出不同的错误提示消息。库函数perror()strerror()提供类似的功能。

  • perror()函数先打印由用户自定义的字符串后,接着输出error值对应的错误提示字符串。

    #include <stdio.h> void perror(const char *msg);

一个使用它的简单的例子:

fd = open(pathname, flags, mode);
if(fd == -1){
   
    perror("open");
    exit(EXIT_FAILURE);
}
  • strerror()函数根据参数值返回特定的错误消息。

    #include <string.h> char *strerror(int errnum); //返回与errnum相应的错误消息的指针

因为strerror函数返回的错误消息字符串所在的空间是静态分配的,所以当前的错误消息会被下一次strerror函数调用所覆盖。

如果传给它的参数errnum是非法的,那么它会返回“Unknown error nnn”,在某些实现上也可能返回NULL指针。

3.介绍几种错误处理包装函数

  • 下文是即将介绍的错误处理的包装函数的头文件error_functions.h,里面声明了7个错误处理函数:

    /* Func Name : error_functions.h Func Describe : Header file for error_functions.c. */ #ifndef ERROR_FUNCTIONS_H #define ERROR_FUNCTIONS_H

    /* Error diagnostic routines */

    void errMsg(const char *format, ...);

    #ifdef GNUC

    /* This macro stops 'gcc -Wall' complaining that "control reaches end of non-void function" if we use the following functions to terminate main() or some other non-void function. */
    

    #define NORETURN attribute ((noreturn)) #else #define NORETURN #endif

    void errExit(const char *format, ...) NORETURN ;

    void err_exit(const char *format, ...) NORETURN ;

    void errExitEN(int errnum, const char *format, ...) NORETURN ;

    void fatal(const char *format, ...) NORETURN ;

    void usageErr(const char *format, ...) NORETURN ;

    void cmdLineErr(const char *format, ...) NORETURN ;

    #endif

  1. errMsg(): 向标准错误终端输出消息,格式为:“错误类型 + 错误信息 + 用户自定义消息 + 换行”。

  2. errExit(): 向标准错误终端输出消息并调用exit()函数或abort()函数(如果环境变量EF_DUMPCORE设置为非空,则会调用该函数生成核转储文件供调试用)终止程序,格式同上。

  3. err_exit(): 除了调用_exit()函数代替exit()函数和输出错误消息时不刷新标准输入输出缓存(stdio buffers),其余同errExit()函数。其主要用于当一个进程创建的一个子进程出错需要终止时,得避免子进程刷新从父进程那继承过来的stdio缓存。

  4. err_ExitEN(): 主要用于执行POSIX标准的程序出错处理,因为它们的返回值代表了errno。

    /此处不用errno的原因是errno其实代表一个宏调用, 这个宏调用会调用一个函数来返回其值,会影响运行效率。/ int s; s = pthread_creat(&thread, NULL, func, &arg); if(s != 0) errExitEN(s, "pthread_creat");

  5. fatal(): 它可以用来诊断一般性错误,包括不设置errno值得库函数运行错误。其余同errExit()函数。

  6. usageErr(): 一般用来诊断命令行命令输入错误(参数使用),格式为“Usage: + 格式化的用户自定义字符串”,而后调用exit()函数终止程序。

  7. cmdLineErr(): 基本同上,其专指命令行错误,它的输出消息的格式为”Command-line usage error: + 格式化的用户自定义字符串“。

下面是它们的具体实现:

/* Func Name : error_functions.c Func Describe : Some standard error handling routines used by various programs. */

#include <stdarg.h>
#include "error_functions.h"
#include "tlpi_hdr.h"
#include "ename.c.inc" /* Defines ename and MAX_ENAME */

#ifdef __GNUC__ /* Prevent 'gcc -Wall' complaining */
__attribute__ ((__noreturn__))  /* if we call this function as last */
#endif /* statement in a non-void function */
static void
terminate(Boolean useExit3)
{
   
    char *s;

    /* Dump core if EF_DUMPCORE environment variable is defined and is a nonempty string; otherwise call exit(3) or _exit(2), depending on the value of 'useExit3'. */

    s = getenv("EF_DUMPCORE");

    if (s != NULL && *s != '\0')
        abort();
    else if (useExit3)
        exit(EXIT_FAILURE);
    else
        _exit(EXIT_FAILURE);
}

/* Diagnose 'errno' error by: * outputting a string containing the error name (if available in 'ename' array) corresponding to the value in 'err', along with the corresponding error message from strerror(), and * outputting the caller-supplied error message specified in 'format' and 'ap'. */

static void
outputError(Boolean useErr, int err, Boolean flushStdout,
        const char *format, va_list ap)
{
   
#define BUF_SIZE 500
    char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE];

    vsnprintf(userMsg, BUF_SIZE, format, ap);

    if (useErr)
        snprintf(errText, BUF_SIZE, " [%s %s]",
                (err > 0 && err <= MAX_ENAME) ?
                ename[err] : "?UNKNOWN?", strerror(err));
    else
        snprintf(errText, BUF_SIZE, ":");

#if __GNUC__ >= 7
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
#endif
    snprintf(buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg);
#if __GNUC__ >= 7
#pragma GCC diagnostic pop
#endif

    if (flushStdout)
        fflush(stdout);       /* Flush any pending stdout */
    fputs(buf, stderr);
    fflush(stderr);           /* In case stderr is not line-buffered */
}

/* Display error message including 'errno' diagnostic, and return to caller */

void
errMsg(const char *format, ...)
{
   
    va_list argList;
    int savedErrno;

    savedErrno = errno;       /* In case we change it here */

    va_start(argList, format);
    outputError(TRUE, errno, TRUE, format, argList);
    va_end(argList);

    errno = savedErrno;
}

/* Display error message including 'errno' diagnostic, and terminate the process */

void
errExit(const char *format, ...)
{
   
    va_list argList;

    va_start(argList, format);
    outputError(TRUE, errno, TRUE, format, argList);
    va_end(argList);

    terminate(TRUE);
}

/* Display error message including 'errno' diagnostic, and terminate the process by calling _exit(). The relationship between this function and errExit() is analogous to that between _exit(2) and exit(3): unlike errExit(), this function does not flush stdout and calls _exit(2) to terminate the process (rather than exit(3), which would cause exit handlers to be invoked). These differences make this function especially useful in a library function that creates a child process that must then terminate because of an error: the child must terminate without flushing stdio buffers that were partially filled by the caller and without invoking exit handlers that were established by the caller. */

void
err_exit(const char *format, ...)
{
   
    va_list argList;

    va_start(argList, format);
    outputError(TRUE, errno, FALSE, format, argList);
    va_end(argList);

    terminate(FALSE);
}

/* The following function does the same as errExit(), but expects the error number in 'errnum' */

void
errExitEN(int errnum, const char *format, ...)
{
   
    va_list argList;

    va_start(argList, format);
    outputError(TRUE, errnum, TRUE, format, argList);
    va_end(argList);

    terminate(TRUE);
}

/* Print an error message (without an 'errno' diagnostic) */

void
fatal(const char *format, ...)
{
   
    va_list argList;

    va_start(argList, format);
    outputError(FALSE, 0, TRUE, format, argList);
    va_end(argList);

    terminate(TRUE);
}

/* Print a command usage error message and terminate the process */

void
usageErr(const char *format, ...)
{
   
    va_list argList;

    fflush(stdout);           /* Flush any pending stdout */

    fprintf(stderr, "Usage: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);           /* In case stderr is not line-buffered */
    exit(EXIT_FAILURE);
}

/* Diagnose an error in command-line arguments and terminate the process */

void
cmdLineErr(const char *format, ...)
{
   
    va_list argList;

    fflush(stdout);           /* Flush any pending stdout */

    fprintf(stderr, "Command-line usage error: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);           /* In case stderr is not line-buffered */
    exit(EXIT_FAILURE);
}

此处ename是一个包含所有错误类型简称字符的数组,定义于ename.c.inc文件中。其用errno值作为索引存储对应的错误类型简称。同时由于不同内核版本即架构的区别,错误类型名称即数值也不同,所以此处提供了一个脚本Build_ename.sh用以产生ename.c.inc文件。

#!/bin/sh
#
# Create a new version of the file ename.c.inc by parsing symbolic
# error names defined in errno.h
# echo '#include <errno.h>' | cpp -dM | 
sed -n -e '/#define *E/s/#define *//p' |sort -k2n |
awk '
BEGIN {
   
        entries_per_line = 4
        line_len = 68;
        last = 0;
        varname =" enames";
        print "static char *ename[] = {";
        line =  " /* 0 */ \"\"";
}
 
{
   
    if ($2 ~ /^E[A-Z0-9]*$/) {
         # These entries are sorted at top
        synonym[$1] = $2;
    } else {
   
        while (last + 1 < $2) {
   
            last++;
            line = line ", ";
            if (length(line ename) > line_len || last == 1) {
   
                print line;
                line = " /* " last " */ ";
                line = sprintf(" /* %3d */ ", last);
            }
            line = line "\"" "\"" ;
        }
        last = $2;
        ename = $1;
        for (k in synonym)
            if (synonym[k] == $1) ename = ename "/" k;
 
            line = line ", ";
            if (length(line ename) > line_len || last == 1) {
   
                print line;
                line = " /* " last " */ ";
                line = sprintf(" /* %3d */ ", last);;
            }
            line = line "\"" ename "\"" ;
    }
}
END {
   
    print  line;
    print "};"
    print "";
    print "#define MAX_ENAME " last;
}
'
  • 下面显示的数组为在环境”Linux 2.6/x86-32“环境下运行脚本产生的ename.c.inc中ename数组的值及errno最大值:

C库函数、系统函数等调用错误的处理方法

  • 有些项为空,代表其对应的errno值未使用;有些项包含两个错误名,代表这两种错误对应同一个errno值。

获取更多知识,请点击关注:
嵌入式Linux&ARM
CSDN博客
简书博客
知乎专栏


点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
Python—执行系统命令的四种方法(os.system、os.popen、commands、subprocess)
一、os.system方法这个方法是直接调用标准C的system()函数,仅仅在一个子终端运行系统命令,而不能获取命令执行后的返回信息。os.system(cmd)的返回值。如果执行成功,那么会返回0,表示命令执行成功。否则,则是执行错误。使用os.system返回值是脚本的退出状态码,该方法在调用完shell脚本后
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
cpp加油站 cpp加油站
3年前
c++动态分配浅析
1.c语言中动态分配和释放在c中,申请动态内存是使用malloc和free,这两个函数是c的标准库函数,分配内存使用的是系统调用,使用它们必须包含stdlib.h,才能编译通过。malloc后需要检查内存是否分配成功,free则要在指针不为空的情况下才能进行。示例代码如下:cinclude<stdio.hinclude<stdlib.hinclude
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
5.1.5 如何使用系统调用
5.1.5 如何使用系统调用如图5.2所示,用户应用可以通过两种方式使用系统调用。第一种方式是通过C库函数,包括系统调用在C库中的封装函数和其他普通函数。!(http://images.51cto.com/files/uploadimg/20100723/125031891.jpg)(https://www.oschina.net/