Zed调试宏 C语言错误日志 异常错误调试信息

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语言项目中的错误处理、调试和日志记录。它通过一系列宏定义提供了易于使用的工具,帮助开发者在程序中更好地管理错误,输出调试信息,以及记录不同级别的日志。每个宏的具体作用如下:

  1. 调试功能 (debug):在调试模式下,通过输出当前文件名和行号以及给定的调试信息,帮助开发者定位和解决问题。NDEBUG宏通常用于控制是否开启调试信息输出,默认情况下在编译时禁用调试输出。

  2. 错误日志功能 (log_err, log_warn, log_info):分别用于输出错误、警告和信息日志。在输出时会附加当前文件名、行号及错误代码errno的描述,方便开发者快速定位问题。

  3. 条件检查 (check, check_mem, check_debug):这些宏用于检查某个条件是否为真,如果条件不满足,则记录错误信息并跳转到error标签。check_mem专门用于检查内存分配是否成功。check_debug则在调试模式下提供额外的信息输出,帮助在调试时更清楚地了解程序的执行状态。

  4. 错误处理 (sentinel)sentinel可以放在函数的任何不应该执行的地方,它会打印错误信息并且跳到error:标签。你可以将它放到if-statements或者switch-statements的不该被执行的分支中,比如default

  5. clean_errno:这个宏用于清理errno的值,如果没有错误,返回"None";如果有错误,返回对应的错误信息。用于获取errno的安全可读的版本

         是的,仅仅22行的代码就可以充当一个错误日志打印的插件,可以完成简化错误处理、日志记录和调试过程,让代码更加清晰和易于维护。并且通过goto error 控制程序的流程,在出现错误时跳转到一个名为 error 的标签。通过使用 goto,程序会跳过剩余的正常流程,直接转到错误处理代码部分。这种做法通常用于清理资源和终止程序执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值