perror函数详解

发布时间:2023-05-18

perror函数是一个类Unix操作系统的C语言标准库函数,用于输出错误信息。当程序产生某个系统调用错误时,perror会自动将系统错误信息转换成可读格式并输出到stderr(标准错误)设备上,通常和errno全局变量一起使用。

一、perror return 1

perror return 1是一个常见的错误信息输出格式,在该错误格式下,程序返回值通常设为1,表示程序的主要逻辑发生错误。通常的错误信息形如“程序名:错误信息:errno!”

int main(int argc, char *argv[]) {
    FILE *fp;
    if ((fp = fopen(argv[1], "r")) == NULL) {
        perror("fopen");
        return 1;
    }
    // ...
    fclose(fp);
    return 0;
}

在以上代码中,程序试图打开一个文件,如果打开文件失败,就会调用perror函数输出错误信息,并返回1。其中'fopen'就是错误信息的前缀,代表文件打开失败,errno则是系统错误码。

二、perror原理分析

在了解perror函数具体实现细节之前,需要先了解几个概念:

  • errno:errno是C/C++语言中的一个全局变量,负责存储当前程序的错误码。在程序调用Linux系统函数时,例如open、read、write等,系统会在发生错误时将错误码存储在该变量中。
  • strerror:strerror函数将错误码转换为一个字符串,该字符串是人类可读的错误信息,比如“文件不存在”、“内存不足”等。 perror函数的原理非常简单,它实际上就是将errno作为strerror的参数,将错误信息转换成一个人类可读的字符串,然后将其输出到stderr设备上:
void perror(const char *s) {
    // 将errno转换为人类可读的字符串
    char *msg = strerror(errno);
    // 将错误信息输出到stderr设备上
    fprintf(stderr, "%s: %s\n", s, msg);
}

三、perror和strerror的实战应用

perror和strerror函数是程序员调试程序时经常使用的工具。在接收到其他程序员提交的crash dump时,通常可以根据错误信息分析出程序的错误原因。 在下面的代码示例中,我们假设需要实现一个HTTP服务器,当服务器接收到客户端发来的一份HTTP请求时,根据请求的URL返回文件内容。在该例子中,我们通过perror和strerror函数处理打开文件错误时的错误信息:

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno;
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t clilen;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("ERROR opening socket");
        exit(1);
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = 8888;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    while (1) {
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
        if (newsockfd < 0) {
            perror("ERROR on accept");
            exit(1);
        }
        // get client request
        char buffer[4096];
        bzero(buffer, sizeof(buffer));
        int n = read(newsockfd, buffer, sizeof(buffer) - 1);
        if (n < 0) {
            perror("ERROR reading from socket");
            exit(1);
        }
        // parse client request
        // ...
        // open file
        char *filename = ...;
        FILE *fp = fopen(filename, "r");
        if (fp == NULL) {
            perror("ERROR opening file");
            exit(1);
        }
        // read file content
        char filebuf[4096];
        bzero(filebuf, sizeof(filebuf));
        fread(filebuf, sizeof(filebuf) - 1, 1, fp);
        // send file content to client
        n = write(newsockfd, filebuf, strlen(filebuf));
        if (n < 0) {
            perror("ERROR writing to socket");
            exit(1);
        }
        fclose(fp);
        close(newsockfd);
    }
    close(sockfd);
    return 0;
}

以上示例中,当程序在打开文件时返回NULL时,perror函数将输出“ERROR opening file: 文件不存在或者权限不足!”的错误信息,并且程序将退出。

四、perror和strerror的另一种使用方式

需要注意的是,perror函数一般都是在函数返回-1时才会被调用。如果errno已经被另外的函数清空了,那么perror的输出结果将是一个错误信息,但是这个错误信息实际上根本跟程序运行无关。 如果我们想调用perror函数以输出一个手动设置的错误信息,那么可以通过在调用函数返回前设置errno的办法完成。

int my_read(int fd, char *buf, size_t len) {
    int n = read(fd, buf, len);
    if (n == -1) {
        errno = 42; // 设置errno为任意非0数值
    }
    return n;
}
int main(int argc, char *argv[]) {
    char buf[4096];
    int n = my_read(STDIN_FILENO, buf, sizeof(buf) - 1);
    if (n == -1) {
        perror("ERROR in my_read");
    }
    return 0;
}

在以上代码示例中,my_read函数会返回-1,表示读取失败。在程序调用perror函数输出错误信息时,会输出“ERROR in my_read: Input/output error!”。

五、总结

C标准库中的perror函数是一个非常实用的函数。通过分析perror函数的原理,可以更好地理解程序在出错时的内部逻辑。在实际开发中,程序员可以通过调用perror函数和strerror函数输出错误信息,方便快速查找程序的错误。