C语言异常错误处理及日志记录

C语言中,异常处理不像高级语言(如Java、Python)那样有内置的机制(如try-catch块)。C语言通常通过返回错误代码或全局变量的方式进行错误处理。为了增强C程序的健壮性,尤其是在大型项目中,需要手动实现错误处理和日志记录机制。本文主要介绍C语言中异常错误处理及日志记录。

1、C语言的错误处理

其它编程语言中,例如,C++,Java,C#,Python等语言中,都可以使用try catch类似语法进行异常处理。C语言中没有异常机制,一般用返回值标识错误,判断返回值进行错误的处理。

#include <stdio.h>

int divide(int a, int b, int* result) {
    if (b == 0) {
        return -1; // 返回错误代码表示除以零错误
    }
    *result = a / b;
    return 0; // 成功返回0
}

int main() {
    int result;
    int error = divide(10, 0, &result);
    if (error != 0) {
        printf("Error: Division by zero!\n");
    } else {
        printf("Result: %d\n", result);
    }
    return 0;
}

返回值表示出错的常见情况可能如下:

1)如果函数返回值是int ,并且返回的数据不可能是负数,直接返回-1表示出错,非负数代表正常返回数据。

2)如果函数返回值是int ,但返回的数据也可能是负数;返回0代表正常,-1代表出错。用指针类型的参数带出返回的数据。

3)如果函数返回值是指针类型,返回NULL(0)代表出错,其他代表正常。

4)如果函数不需要考虑出错情况,可以用void返回值。

2、错误异常处理函数及全局变量

C标准中,对于错误处理提供了一个全局变量和3个函数:

1)全局变量

errno:外部的全局变量,用于储存错误的编号。errno 是一个标准库全局变量,用于表示最后一次系统调用的错误代码。C库函数在某些情况下会设置 errno,以标识发生的错误。可以使用 <errno.h> 中定义的错误代码

2)异常错误处理函数

strerror()perror()printf() 都可以把错误的编号转换成错误的信息。

strerror():传入一个错误的编号,返回一个错误的信息

perror():打印当前的错误信息(自动查找errno得到的错误编号)

printf("%m"):打印当前错误信息(自动查找errno)

例如,

#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno ;
int main ()
{
   FILE * pf;
   int errnum;
   pf = fopen ("cjavapy.txt", "rb");
   if (pf == NULL)
   {
      errnum = errno;
      fprintf(stderr, "错误号: %d\n", errno);
      perror("通过 perror 输出错误");
      fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
   }
   else
   {
      fclose (pf);
   }
   return 0;
}

3、程序退出状态值

通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0

如果程序中存在一种错误情况,当退出程序时,会带有状态值 EXIT_FAILURE,被定义为 -1

例如,

#include <stdio.h>
#include <stdlib.h>
main()
{
   int dividend = 20;
   int divisor = 5;
   int quotient;
   if( divisor == 0){
      fprintf(stderr, "除数为 0 退出运行...\n");
      exit(EXIT_FAILURE);
   }
   quotient = dividend / divisor;
   fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
   exit(EXIT_SUCCESS);
}

4、日志记录

为增强调试和错误追踪,通常需要实现自定义的错误处理和日志记录机制。日志记录可以帮助我们在程序运行过程中追踪和记录错误信息。C语言中记录日志能帮助程序员在程序开发时调试、记录及监控数据。程序在正式运行时,也方便能过日志来排查问题。

1)简单的错误处理函数

可以实现一个统一的错误处理函数,打印错误信息并返回错误代码,如下,

#include <stdio.h>
#include <stdlib.h>

void handle_error(const char *message) {
    perror(message); // 输出错误信息,并根据 errno 的值打印系统级错误信息
    exit(EXIT_FAILURE); // 终止程序
}

int main() {
    FILE *file = fopen("non_existent_file.txt", "r");
    if (file == NULL) {
        handle_error("File open failed");
    }
    fclose(file);
    return 0;
}

2)日志记录

日志系统可以用来记录运行时的状态、错误信息或调试信息。常见的方法是将日志输出到文件中。

#include <stdio.h>
#include <time.h>
#include <stdarg.h>

// 简单的日志记录函数,将日志写入文件
void log_message(const char *format, ...) {
    FILE *logfile = fopen("program.log", "a");
    if (logfile == NULL) {
        perror("Error opening log file");
        return;
    }

    // 获取当前时间并写入日志文件
    time_t now;
    time(&now);
    fprintf(logfile, "%s: ", ctime(&now)); // 写入时间戳

    // 使用 va_list 来处理变长参数
    va_list args;
    va_start(args, format);
    vfprintf(logfile, format, args);
    va_end(args);

    fprintf(logfile, "\n");
    fclose(logfile);
}

int main() {
    FILE *file = fopen("non_existent_file.txt", "r");
    if (file == NULL) {
        log_message("Error: Failed to open file '%s'", "non_existent_file.txt");
        return -1;
    }
    fclose(file);
    log_message("File opened successfully.");
    return 0;
}

3)结合 assert 进行调试

assert 是 C 标准库提供的一种调试辅助工具。它用于在开发过程中检查条件是否成立,如果条件不成立,程序会终止并显示错误信息。assert 通常用于开发和测试阶段,在发布版本中可以通过定义 NDEBUG 来禁用。

#include <stdio.h>
#include <assert.h>

int main() {
    int a = 5;
    int b = 0;

    assert(b != 0); // 如果 b == 0,程序终止并显示错误信息
    printf("a / b = %d\n", a / b);

    return 0;
}

4)完整日志记录实现

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
enum {ERROR,WARNING,INFO,DEBUG};
//宏定义形式
#define LogRecord(_error_level,_fmt,_X...) \
    do { \
        time_t _current_time=time(NULL); \
        char _file_name[256]="\0"; \
        strftime(_file_name,sizeof(_file_name),"%Y-%m-%d_log_record.log",localtime(&_current_time)); \
        FILE *_fp=NULL; \
        _fp=fopen(_file_name,"a+"); \
        if(_fp!=NULL) \
        { \
            char _time_str[32]; \
            strftime(_time_str,sizeof(_time_str),"%Y-%m-%d %H:%M:%S",localtime(&_current_time)); \
            if(_error_level==ERROR) \
            { \
                fprintf(_fp,"[%s]-[%s]-[%s]-[%d] :> "_fmt"\n",_time_str,"ERROR",__FILE__,__LINE__,##_X); \
            } \
            else if(_error_level==WARNING) \
            { \
                fprintf(_fp,"[%s]-[%s]-[%s]-[%d] :> "_fmt"\n",_time_str,"WARNING",__FILE__,__LINE__,##_X); \
            } \
            else if(_error_level==INFO) \
            { \
                fprintf(_fp,"[%s]-[%s]-[%s]-[%d] :> "_fmt"\n",_time_str,"INFO",__FILE__,__LINE__,##_X); \
            } \
            else if(_error_level==DEBUG) \
            { \
                fprintf(_fp,"[%s]-[%s]-[%s]-[%d] :> "_fmt"\n",_time_str,"DEBUG",__FILE__,__LINE__,##_X); \
            } \
            fclose(_fp); \
        } \
    }while(0);
//函数形式
void log_reocrd(int error_level, const char *format, ...)
{
    va_list args;
    FILE *fp=NULL;
    char time_str[32];
    char file_name[256];
    va_start (args, format);
    time_t time_log = time(NULL);
    strftime(file_name,sizeof(file_name),"%Y-%m-%d_log_history.log",localtime(&time_log));
    if((fp=fopen(file_name,"a+"))!=NULL)
    {
        strftime(time_str,sizeof(time_str),"%Y-%m-%d %H:%M:%S",localtime(&time_log));
        if(error_level==(int)ERROR)
        {
            fprintf (fp, "[%s]-[%s]-[%s]-[%d] :> ",time_str,"ERROR",__FILE__,__LINE__);
            vfprintf (fp,format,args);
            fprintf (fp,"\n");
        }
        else if(error_level==(int)WARNING)
        {
            fprintf (fp, "[%s]-[%s]-[%s]-[%d] :> ",time_str,"WARNINGs",__FILE__,__LINE__);
            vfprintf (fp,format,args);
            fprintf (fp,"\n");
        }
        else if(error_level==(int)INFO)
        {
            fprintf (fp, "[%s]-[%s]-[%s]-[%d] :> ",time_str,"INFO",__FILE__,__LINE__);
            vfprintf (fp,format,args);
            fprintf (fp,"\n");
        }
        else if(error_level==(int)DEBUG)
        {
            fprintf (fp, "[%s]-[%s]-[%s]-[%d] :> ",time_str,"DEBUG",__FILE__,__LINE__);
            vfprintf (fp,format,args);
            fprintf (fp,"\n");
        }
        fclose(fp);
    }
    va_end (args);
}
int main()
{
    int i=10;
    double x=3.1415926;
    long long k=123144345;
    LogRecord(INFO,"i=%d",i)
    LogRecord(INFO,"x=%lf",x)
    LogRecord(INFO,"k=%lld",k)
    LogRecord(INFO,"cjavapy")
    LogRecord(INFO,"www.cjavapy.com",NULL)
    log_reocrd(INFO,"i=%d",i);
    log_reocrd(INFO,"x=%lf",x);
    log_reocrd(INFO,"k=%lld",k);
    log_reocrd(INFO,"cjavapy");
    log_reocrd(INFO,"www.cjavapy.com",NULL);
    printf("%s","finish");
    return 0;
}

推荐阅读
cjavapy编程之路首页