C/C++如何写调试宏

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,表示这样的代码肯定不是做循环用的,它有什么用呢?

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

假如你定义一个这样宏,本意是调用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)则能避免这些错误,所以复杂宏定义经常使用它。

消除分支语句或者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;

}

使用代码块,代码块内定义变量,不用考虑变量重复问题

显而易见。

4. 参考博文

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

相关推荐

为什么全民枪战不火了?
365bet体育滚球

为什么全民枪战不火了?

📅 07-20 👀 9772
如何进入非好友QQ空间
365bet体育滚球

如何进入非好友QQ空间

📅 07-04 👀 5488
0910是哪个城市的区号
365wm完美体育

0910是哪个城市的区号

📅 07-31 👀 2019