首页 > 编程语言 >C/C++如何写调试宏

C/C++如何写调试宏

时间:2024-04-30 22:25:43浏览次数:28  
标签:__ func2 int 代码 C++ 如何 bOk DEBUG 调试

1. 调试宏以及测试

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

#ifndef __DEBUG_H__
#define __DEBUG_H__

#include <iostream>
#include <string>
#include <stdio.h>

// 定义日志级别枚举
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

标签:__,func2,int,代码,C++,如何,bOk,DEBUG,调试
From: https://www.cnblogs.com/wangxinzhi/p/18168764

相关文章

  • 如何将家用电脑改装成工控电脑
    工控电脑和家用电脑除了主板或许更厚些(这也只是可能),CPU更慢些,功率更低一些以外最大的区别就是工控电脑有GPIO,也就是可以有接口进行直接的硬件控制,之前自己也是玩树莓派的,而且主要就是奔着这个GPIO去的,说实话,对于个人玩家来说这个所谓的功耗和待机耗能好像也都不是重点,但是今天突然......
  • 《Effective C++》第三版-3. 资源管理(Resource Management)
    目录条款13:以对象管理资源(Useobjectstomanageresources)关键想法智能指针条款14:在资源管理类中小心copying行为(Thinkcarefullyaboutcopyingbehaviorinresource-managingclasses)条款15:在资源管理类中替工对原始资源的访问(Provideaccesstorawresourcesinresource-ma......
  • rust+stm32+vscode搭建开发调试环境
    1.安装rustrust官网传送门2.安装openocd安装openocd传送门3.安装stlink安装stlink传送门4.搭建gcc-arm-none-eabi编译环境搭建gcc-arm-none-eabi编译环境5.安装vscodevscode官网传送门6.安装相关插件rust-analyzer:使用VSCode开发Rust必备cortex-debug:调试、debug嵌入......
  • 技术探秘:如何利用仪表构造InfiniBand流量在数据中心测试中的应用
    一、什么是Infiniband?在当今数据爆炸的时代,数据中心作为信息处理的中心枢纽,面临着前所未有的挑战。传统的通信方式已经难以满足日益增长的数据传输需求,而InfiniBand技术的出现,为数据中心带来了全新的通信解决方案。InfiniBand(IB)是一种高性能计算和数据中心网络架构,其设计目标是......
  • 学习 C++,从搭建 Visual Studio Code 开始
    0.声明本文针对Windows和Linux系统配置VisualStudioCode,Mac贵族请勿入内。本文以Windows10系统演示。1.准备工作1.1.安装VisualStudioCodeWindows:官网下载链接选择Windows(Windows10,11)进行安装Linux:在应用商店搜索VisualStudioCode,安装即......
  • 你“系鞋带”需要多长时间?写一个规范,能够让外星人理解并执行如何“系鞋带”不同的同学
    一、问题要求:你“系鞋带”需要多长时间?写一个规范,能够让外星人理解并执行如何“系鞋带”不同的同学可以针对不同类型的鞋设计一个规格说明(10分钟时间)与你的其他同学进行比较,选出较佳者告诉我们优劣的原因优秀者的优点一般者的弱点二、我的答案:为了提供一份详尽的“系鞋带......
  • useEffect中的deps数组经常依赖了好多变量,甚至包括对象,如何避免这样,假如某个变量变化
    避免在useEffect的依赖数组中包含大量变量或对象,可以通过以下几种策略来优化:拆分useEffect:如果不同的副作用依赖于不同的状态或变量,可以将它们拆分为多个useEffect调用。这样每个useEffect只关注自己关心的依赖项,使逻辑更加清晰且易于维护。useEffect(()=>{//仅当a变化......
  • C/C++、Java 与 Python 中未初始化变量的处理比较
    在C/C++中,未初始化的变量的值是不确定的,可能是随机的。 在Python中,如果直接使用未初始化的变量,会引发NameError异常。Python要求变量在使用前必须进行赋值或初始化。 而在Java中,直接使用未初始化的局部变量会导致编译错误,必须先对变量进行初始化。 C++和Java在字......
  • 通义灵码实战系列:一个新项目如何快速启动,如何维护遗留系统代码库?
    作者:别象进入2024年,AI热度持续上升,翻阅科技区的文章,AI可谓是军书十二卷,卷卷有爷名。而麦肯锡最近的研究报告显示,软件工程是AI影响最大的领域之一,AI已经成为了软件工程的必选项,也有研究称开发者每天的事务性工作可能占到了七成左右,比如单侧编写等,而这部分恰好是AI所擅长......
  • 通义灵码实战系列:一个新项目如何快速启动,如何维护遗留系统代码库?
    作者:别象进入2024年,AI热度持续上升,翻阅科技区的文章,AI可谓是军书十二卷,卷卷有爷名。而麦肯锡最近的研究报告显示,软件工程是AI影响最大的领域之一,AI已经成为了软件工程的必选项,也有研究称开发者每天的事务性工作可能占到了七成左右,比如单侧编写等,而这部分恰好是AI所擅长......