Zed的强大的调试宏

#为什么需要错误处理

大多数程序员假定错误不会发生,并且这一乐观的思想影响了他们所用和创造的语言。

C通过返回错误码或设置全局的errno值来解决这些问题,并且你需要检查这些值。
通过这种机制检查现存的复杂代码中,你所执行的东西是否发生错误。模式如下

  • 调用函数
  • 如果返回值出现错误(每次必须检查)
  • 清理创建的所有资源
  • 打印出所有可能有帮助的错误信息

接下来将专注于这些步骤的实现

实现错误机制意味着每个函数调用结束后都需要3~4行额外代码,太麻烦。使用一系列“调试宏”作为解决方案。

几个重要的认知:

  • 错误检查可以在代码里直接写出,不过通常需要3~4行
  • 利用宏(而不是函数)来实现是由许多优势的
    • 简洁
    • 错误处理时需要用到file:line信息,如果在函数内部执行这些会得到错误信息
    • goto函数的实现会相当麻烦
  • goto 可以和标签结合使用
  • 宏的使用也有相当多的技巧,与函数有异曲同工之妙,但在使用方式上更加灵活(某些方面)

宏定义

#ifndef __dbg_h__
#define __dbg_h__

#include 
#include    //引入errno变量
#include 

#ifdef NDEBUG       //可以通过宏定义来消除所有调试信息
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
        //输出错误信息,文件名,行号等。其中:##__VA_ARGS__表示宏中...   可以类似调用printf使用debug
#endif

#define clean_errno() (errno == 0 ? "None" : strerror(errno))

#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define sentinel(M, ...)  { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define check_mem(A) check((A), "Out of memory.")

#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }

#endif

代码解释

头文件

errno不是一个库,而是一个全局变量。
当调用一些系统库函数时,他们可能会设置errno的值来指示错误的类型。
通常,成功完成函数调用时会置零。
使用实例

#include 
#include 
#include 

int main() {
    FILE *file = fopen("nonexistent_file.txt", "r");

    if (file == NULL) {
        perror("Error opening file");    //自动根据error输出错误信息,终端显示:Error opening file: No such file or directory
        fprintf(stderr, "errno = %d\n", errno);  //输出到标准输出流,终端显示:error = x
        exit(EXIT_FAILURE);
    }

    // Use the file...

    fclose(file);
    return 0;
}

综上理解:errno是一个全局变量,可以用来获取函数执行失败后的错误信息代号。
通常搭配perrorfprintf(stderr, " xxx%dxxx " , errno)使用
搭配strerror(errno)使用,返回对应的错误字符串`

debug宏

#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

输出错误信息到终端:__FILE__代表文件名,__LINE__代表行号,##__VA_ARGS__代表可变参数部分...
这里对于debug的使用类似于调用printf

  • M是想要输出的错误字符串,注意不带换行符。
  • 后续可变参数可以替换M 中格式化预留的内容。

实例:
debug("I am %d years old.", 37);
终端输出
DRBUG ex20.c:11: I am 37 years old

dbg.h

#ifndef __dbg_h__
#define __dbg_h__

#include 
#include 
#include 

#ifdef NDEBUG
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#endif

#define clean_errno() (errno == 0 ? "None" : strerror(errno))

#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)   //以上三条用于输出错误、警告等信息。

#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }  //完成输出信息、置零errno、跳转知至error标签

#define sentinel(M, ...)  { log_err(M, ##__VA_ARGS__); errno=0; goto error; }     //可以放在不应该执行的地方,如 default

#define check_mem(A) check((A), "Out of memory.")

#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }

#endif

使用案例#include “dbg.h”

#include"dbg.h"
#include 
#include 


void test_debug()
{
    // notice you don't need the \n
    debug("I have Brown Hair.");

    // passing in arguments like printf
    debug("I am %d years old.", 37);              //debug调用类似printf
}

void test_log_err()
{
    log_err("I believe everything is broken.");
    log_err("There are %d problems in %s.", 0, "space");     //调用类似printf,输出错误信息,包含行号等
    
}

void test_log_warn()
{
    log_warn("You can safely ignore this.");
    log_warn("Maybe consider looking at: %s.", "/etc/passwd");
}

void test_log_info()
{
    log_info("Well I did something mundane.");
    log_info("It happened %f times today.", 1.3f);
}

int test_check(char *file_name)
{
    FILE *input = NULL;
    char *block = NULL;

    block = malloc(100);
    check_mem(block); // should work               //check_mem紧跟在分配空间之后

    input = fopen(file_name,"r");
    check(input, "Failed to open %s.", file_name);      //check文件紧跟在打开文件之后

    free(block);
    fclose(input);
    return 0;

error:                              //error用于清空可能分配出去的空间
    if(block) free(block);
    if(input) fclose(input);
    return -1;
}

int test_sentinel(int code)
{
    char *temp = malloc(100);
    check_mem(temp);

    switch(code) {
        case 1:
            log_info("It worked.");
            break;
        default:
            sentinel("I shouldn't run.");
    }

    free(temp);
    return 0;

error:
    if(temp) free(temp);
    return -1;
}

int test_check_mem()
{
    char *test = NULL;
    check_mem(test);

    free(test);
    return 1;

error:
    return -1;
}

int test_check_debug()
{
    int i = 0;
    check_debug(i != 0, "Oops, I was 0.");

    return 0;
error:
    return -1;
}

int main(int argc, char *argv[])
{
    check(argc == 2, "Need an argument.");

    test_debug();
    test_log_err();
    test_log_warn();
    test_log_info();

    check(test_check("ex20.c") == 0, "failed with ex20.c");  //check给条件码和错误信息
    check(test_check(argv[1]) == -1, "failed with argv");
    check(test_sentinel(1) == 0, "test_sentinel failed.");
    check(test_sentinel(100) == -1, "test_sentinel failed.");
    check(test_check_mem() == -1, "test_check_mem failed.");
    check(test_check_debug() == -1, "test_check_debug failed.");

    return 0;

error:
    return 1;
}

注意使用到errno的check等宏一定要紧跟在需要判断的函数后面。如空间分配后,文件打开后。
check给条件码和错误信息。

你可能感兴趣的:(linux,算法,c语言)