1、C中的错误码
在C语言中通过返回错误码或设置全局的errno
值来反馈错误问题。errno.h
是一个头文件,它定义了一个全局变量errno
,用于在程序中记录和报告错误的原因。这个机制主要用于处理系统调用或标准库函数出错时的错误反馈。当系统调用或库函数遇到错误时,它通常不会直接返回错误信息,而是通过设置errno
的值来告知程序具体的错误原因。
errno
在每个程序运行时由操作系统维护。它的值代表了最后一次系统调用或标准库函数失败时的错误类型。每当某个系统调用或库函数返回错误时,errno
就会被设置为一个与错误相关的特定值。
错误码: errno
的值是一个整数,它对应着不同的错误类型,每种错误都有一个对应的宏定义。这些宏通常定义在errno.h
文件中,例如:
EINVAL
:无效参数。ENOMEM
:内存不足。EIO
:输入输出错误。EBADF
:文件描述符无效。EACCESS
:没有权限访问文件
这些宏是常量值,为负值,每个宏的名字对应一个特定的错误情况。 当某个函数调用失败并且设置了errno
后,程序可以通过检查errno
的值来确定错误的类型。常用的做法是调用perror()
或者strerror()
来输出错误信息。
-
perror()
:它会根据errno
的值打印出对应的错误消息,并且可以在错误信息前添加一个自定义的描述字符串。例如:
perror("Error opening file");
这行代码会输出类似以下的错误信息:
Error opening file: No such file or directory
-
strerror()
:它返回一个指向静态字符串的指针,表示与errno
值对应的错误信息。例如:printf("Error: %s\n", strerror(errno));
2、使用Zed来打印错误信息
其它语言通过异常来解决这个问题,但是这些问题也会在C中出现(其它语言也一样)。在C中你只能够返回一个值,但是异常是基于栈的返回系统,可以返回任意值。C语言中,尝试在栈上模拟异常非常困难,并且其它库也不会兼容。
解决方案是,使用一系列“调试宏”,它们在C中实现了基本的调试和错误处理系统。这个系统非常易于理解,兼容于每个库,并且使C代码更加健壮和简洁。
它通过实现一系列转换来处理错误,任何时候发生了错误,你的函数都会跳到执行清理和返回错误代码的“error:”区域。你可以使用check
宏来检查错误代码,打印错误信息,然后跳到清理区域。你也可以使用一系列日志函数来打印出有用的调试信息。
#ifndef __dbg_h__ // 如果没有定义 __dbg_h__,则开始宏定义,防止重复引用
#define __dbg_h__#include <stdio.h> // 引入标准I/O库,用于输出日志信息。
#include <errno.h> // 引入errno.h,用于访问错误代码(errno)。
#include <string.h> // 引入string.h,用于处理字符串函数(例如strerror())。// 如果没有定义NDEBUG(即没有禁用调试信息),则定义debug宏。
// debug宏将输出当前文件和行号以及调试信息,方便调试时跟踪。
#ifdef NDEBUG
#define debug(M, ...) // 如果定义了NDEBUG,则禁用调试输出
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
// 否则,输出调试信息,包括文件名、行号。
//##__VA_ARGS__,它告诉预处理器将...所在位置的参数注入到fprintf调用的相应位置
#endif// clean_errno宏:如果errno为0(表示没有错误),则返回"None";否则,返回通过strerror()获取的错误描述字符串。
#define clean_errno() (errno == 0 ? "None" : strerror(errno))// log_err宏:输出错误日志,格式为"[ERROR] (文件名:行号:errno描述) 错误信息"。
#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)// log_warn宏:输出警告日志,格式为"[WARN] (文件名:行号:errno描述) 警告信息"。
#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)// log_info宏:输出普通信息日志,格式为"[INFO] (文件名:行号) 信息"。
#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)// check宏:检查条件A是否为真。如果不为真,输出错误信息并跳转到error标签。
// 它帮助简化了条件检查和错误处理。
#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }// sentinel宏:用于关键错误发生时的直接跳转。类似于check宏,但通常用于不应继续执行的情况。
#define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }// check_mem宏:用于检查内存分配是否成功。如果分配失败,记录错误信息并跳转到error标签。
#define check_mem(A) check((A), "Out of memory.")// check_debug宏:类似于check宏,但它会在调试模式下打印调试信息,帮助在调试阶段追踪问题。
#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }#endif // 结束宏定义
这段代码的作用是简化C语言项目中的错误处理、调试和日志记录。它通过一系列宏定义提供了易于使用的工具,帮助开发者在程序中更好地管理错误,输出调试信息,以及记录不同级别的日志。每个宏的具体作用如下:
-
调试功能 (
debug
):在调试模式下,通过输出当前文件名和行号以及给定的调试信息,帮助开发者定位和解决问题。NDEBUG
宏通常用于控制是否开启调试信息输出,默认情况下在编译时禁用调试输出。 -
错误日志功能 (
log_err
,log_warn
,log_info
):分别用于输出错误、警告和信息日志。在输出时会附加当前文件名、行号及错误代码errno
的描述,方便开发者快速定位问题。 -
条件检查 (
check
,check_mem
,check_debug
):这些宏用于检查某个条件是否为真,如果条件不满足,则记录错误信息并跳转到error
标签。check_mem
专门用于检查内存分配是否成功。check_debug
则在调试模式下提供额外的信息输出,帮助在调试时更清楚地了解程序的执行状态。 -
错误处理 (
sentinel
):sentinel
可以放在函数的任何不应该执行的地方,它会打印错误信息并且跳到error:
标签。你可以将它放到if-statements
或者switch-statements
的不该被执行的分支中,比如default
。 -
clean_errno
:这个宏用于清理errno
的值,如果没有错误,返回"None";如果有错误,返回对应的错误信息。用于获取errno
的安全可读的版本
是的,仅仅22行的代码就可以充当一个错误日志打印的插件,可以完成简化错误处理、日志记录和调试过程,让代码更加清晰和易于维护。并且通过goto error
控制程序的流程,在出现错误时跳转到一个名为 error
的标签。通过使用 goto
,程序会跳过剩余的正常流程,直接转到错误处理代码部分。这种做法通常用于清理资源和终止程序执行。