• C/C++如何写调试宏


    1. 调试宏以及测试

    在写代码时,不可避免需要打印提示、警告、错误等信息,且要灵活控制打印信息的级别。另外,还有可能需要使用宏来控制代码段(主要是调试代码段)是否执行。为此,本文提供一种调试宏定义方案,包括打印字符串信息LOG1宏和格式化打印LOG2宏,且能通过宏控制代码段执行。完整代码如下:

    #ifndef __DEBUG_H__
    #define __DEBUG_H__
    
    #include 
    #include 
    #include 
    
    // 定义日志级别枚举
    enum LogLevel
    {
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL
    };
    
    // 全局日志级别变量声明
    extern LogLevel globalLogLevel;
    
    // 定义日志宏1
    #define LOG1(level, message) do { \
        if (level >= globalLogLevel) { \
            std::cout << "[" #level "] " << __func__ << ":" << __LINE__ << " " << message << std::endl; \
        } \
    } while (0)
    
    // 定义日志宏2
    // stdout带缓冲,按行刷新,fflush(stdout)强制刷新
    // stderr不带缓冲,立刻刷新到屏幕
    #define LOG2(level, format, args...) do { \
        if (level >= globalLogLevel) { \
            fprintf(stderr, "[" #level "] %s:%d " format "\r\n", __func__, __LINE__, ##args); \
        } \
    } while (0)
    
    // 通过宏控制调试代码是否执行
    #define EXECUTE
    
    #ifdef EXECUTE
    #define DEBUG_EXECUTE(code) {code}
    #else
    #define DEBUG_EXECUTE(code)
    #endif
    
    #endif

    在main文件进行宏定义测试,需要定义全局日志级别,以INFO为例,则DEBUG信息不打印。测试文件如下:

    #include "debug.h"
    
    // 全局日志级别变量定义
    LogLevel globalLogLevel = INFO;
    
    int main(void)
    {
        LOG1(DEBUG, "DEBUG message");
        LOG1(INFO, "INFO message");
        LOG1(WARN, "WARN message");
        LOG1(ERROR, "ERROR message");
        LOG1(FATAL, "FATAL message");
    
        int num = 10;
        LOG2(INFO, "num: %d", num);
    
        DEBUG_EXECUTE(
            LOG2(ERROR, "debug execute");
        )
    }

    2. 宏定义小细节

    2.1 #和##

    两者都是预处理运算符

    • #是字符串化运算符,将其后的宏参数转换为用双括号括起来的字符串。
    • ##是符号连接运算符,用于连接两个标记(标记不一定是宏变量,可以是标识符、关键字、数字、字符串、运算符)为一个标记。

    在第一章中使用#把日志级别变量转为字符串,##的作用是在可变参数为0是,删除前面的逗号,只输出字符串。

    2.2 do while(0)

    do while常用来做循环,而while参数为0,表示这样的代码肯定不是做循环用的,它有什么用呢?

    1. 辅助定义复杂宏,避免宏替换出错

    假如你定义一个这样宏,本意是调用DOSOMETHING时执行两个函数。

    #define DOSOMETHING() \
    			func1(); \
    			func2();

    但在类似如下使用宏的代码,宏展开时func2无视判断条件都会执行。

    if (0 < a)
    	DOSOMETHING();
    
    // 宏展开后
    if (0 < a)
        func1();
    func2();

    优化一下,用{}包裹宏是否可行呢?如下:

    #define DOSOMETHING() { \
    			func1(); \
    			func2();}

    由于我们写代码习惯在语句后加分号,你可能会有如下的展开后编译错误。

    if(0 < a)
        DOSOMETHING();
    else
       ...
    
    // 宏展开后
    
    if(0 < a)
    {
        func1();
        func2();
    }; // 错误处
    else
        ...
    

    而do while (0)则能避免这些错误,所以复杂宏定义经常使用它。

    1. 消除分支语句或者goto语句,提高代码的易读性

    如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:

    bool Execute()
    {
       // 分配资源
       int *p = new int;
       bool bOk(true);
     
       // 执行并进行错误处理
       bOk = func1();
       if(!bOk) 
       {
          delete p;   
          p = NULL;
          return false;
       }
     
       bOk = func2();
       if(!bOk) 
       {
          delete p;   
          p = NULL;
          return false;
       }
     
       // 执行成功,释放资源并返回
        delete p;   
        p = NULL;
        return true;
       
    }

    这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。于是我们想到了goto:

    bool Execute()
    {
       // 分配资源
       int *p = new int;
       bool bOk(true);
     
       // 执行并进行错误处理
       bOk = func1();
       if(!bOk) goto errorhandle;
     
       bOk = func2();
       if(!bOk) goto errorhandle;
     
       // 执行成功,释放资源并返回
        delete p;   
        p = NULL;
        return true;
     
    errorhandle:
        delete p;   
        p = NULL;
        return false;
       
    }

    代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢,请看do...while(0)

    bool Execute()
    {
       // 分配资源
       int *p = new int;
     
       bool bOk(true);
       do
       {
          // 执行并进行错误处理
          bOk = func1();
          if(!bOk) break;
     
          bOk = func2();
          if(!bOk) break;
     
       }while(0);
     
        // 释放资源
        delete p;   
        p = NULL;
        return bOk;
       
    }
    1. 使用代码块,代码块内定义变量,不用考虑变量重复问题

    显而易见。

    4. 参考博文

    https://blog.csdn.net/keep_contact/article/details/127838298


    __EOF__

  • 本文作者: Wang Xinzhi
  • 本文链接: https://www.cnblogs.com/wangxinzhi/p/18168764
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    【problem】解决idea提示Method breakpoints may dramatically slow down debugging
    JavaScript实现网页截屏的5种方法(详解+代码)
    多核处理器
    k8s中kubeconfig的配置以及使用详解
    【金融行业】贷款组合绩效如何优化?Qlik辅助搭建动态监控方案
    构建自定义ChatGPT,微软推出Copilot Studio
    系统架构师2022年案例分析考前冲刺
    Linux性能优化--实用工具:性能工具助手
    【设计模式】十、组合模式
    【VS Code 使用tensorboard功能技巧】
  • 原文地址:https://www.cnblogs.com/wangxinzhi/p/18168764