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

C/C++如何写调试宏的更多相关文章

  1. 一道C语言面试题:写一个宏,将16位的整数转为Big Endian

    题目:输入16位整数x,如0x1234,将其转为Big Endian格式再输出,此例为输出 0x3412 来源:某500强企业面试题目 思路:将x左移8位得到a,将x右移8位得到b,a+b即为所得 / ...

  2. preprocessor设置调试宏

    调试宏:preprocessor设置 预处理器“调试”宏在Xcode项目模板的调试版本定义.预处理宏在编译时被解释和调试宏可以用来允许调试代码运行在调试版本中你的项目.如果你不确定你的项目已经确定,可 ...

  3. Linux内核调试方法总结之调试宏

    本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...

  4. 在非MFC程序中使用调试宏 ASSERT(),VERIFY()和 TRACE()

    游戏制作已经开始采用C++了,却鲜有人选择使用MFC.但笔者觉得的 ASSERT(),VERIFY()和 TRACE()这几个宏很好用.所以就想自己写一个版本来适应Windows平台下不同的工程类型. ...

  5. MFC程序中使用调试宏ASSERT()、ASSERT_VALID()、VERIFY()和TRACE()的区别

    其实这篇文章说的很明白了:http://dev.gameres.com/Program/Other/DebugMacro.htm 结论如下: 1.ASSERT()测试它的参数,若参数为0,则中断执行并 ...

  6. 手把手教你写Windows 64位平台调试器

    本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. ------------------------------------------------------- ...

  7. linux 内核(系统)、函数的理解、宏的程序调试

    1.操作系统 1.1.Linux 内核(系统)的组成的部分: 内核主要有:进程调度.内存管理.虚拟文件系统.网络接口和进程通信五个部分组成. (1)进程调度 进程调度是CPU对多个进程对CPU访问的调 ...

  8. [置顶] 宏途_LCD调试流程.

    今天在调试宏途的LCD屏时,开始是开机屏幕不亮,背光都不亮,可能板子已经损坏,一般通过测试电流电压简单验证,(注:硬件引脚没焊好也会引起读lcd id出现错误!!!)出现这个问题一般是因为引脚没焊好, ...

  9. masm中list文件和宏的一些常用编译调试查看方法

    我们知道使用用 ml /Fl a.asm 可以生成lst文件,但是如果不加调整,masm默认生成的lst文件是非常大的,因为它包含了很大的windows必须用到的头文件内容,为了减小lst文件大小,便 ...

  10. python flask框架学习(三)——豆瓣微信小程序案例(一)templates的使用,宏的使用,前端后台传数据,前端写python语句

    目录 一.templates的使用 (1)在templates里创建一个index.html (2)再在app.py里写 (3)展示效果 二.构建第一个电影评分 (1)准备好素材放进static里的i ...

随机推荐

  1. C# 二维码生成、识别,去除白边、任意颜色

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  2. java中 YYYY与yyyy 区别

    大多数情况下格式日期都是用的SimpleDateFormat,比如说把一个日期格式成"yyyy-MM-dd"的形式. 对于年份来说,大写的Y和小写的y其意义是不同的. y 是Yea ...

  3. quartus之ram的IP测试

    quartus之ram的IP测试 1.基本原理 ram,读取存储器,用于储存数据.基本的原理就是使用时钟驱动时序,利用地址区分位置,使用使能控制写入.输出的结果以写入的位宽输出. 2.实际操作 顶层代 ...

  4. AXI自定义IP之UART调试

    AXI自定义IP之UART调试 1.实验原理 前面的自定义IP中已经将AXI总线的大部分接口设置都一一验证了.基本掌握了关键接受寄存器slv_reg和发送寄存器data_reg_out,可以基本实现简 ...

  5. Python---flask框架实现修改密码功能

    数据库部分: 1 #重置密码 2 def reset_pass(phone,password): 3 conn,cursor=get_conn() 4 sql="update userdat ...

  6. MySQL访问控制和用户管理

    访问控制   你需要给用户提供他们所需的访问权,且仅提供他们所需的访问权.这就是所谓的访问控制,管理访问控制需要创建和管理用户账号. 多数用户只需要对表进行读和写,但少数用户甚至需要能创建和删除表: ...

  7. 进程管理与 SELinux

    进程管理与 SELinux   在 Linux 系统当中:『触发任何一个事件时,系统都会将他定义成为一个进程,并且给予这个进程一个 ID ,称为 PID,同时依据启发这个进程的用户与相关属性关系,给予 ...

  8. OpenHarmony创新赛 | 您有一份创新激励奖待领取 请查收!

      2023开源和信息消费大赛 开放原子开源大赛OpenHarmony创新赛 (以下简称"OpenHarmony创新赛") 正如火如荼的进行当中 赛程也即将进入到提交作品的关键阶段 ...

  9. java内存模型(jmm)概念初探

    1.和java内存结构的区别: 很多人会把jmm和Java内存结构搞混,网上搜到的一些文章也是如此,java内存结构就是我们常说的堆,栈,方法区,程序计数器..., 当jvm虚拟机启动的时候,会初始化 ...

  10. 【原创】win11完美搭建macos13实现IOS自动化测试环境

    虚拟机完美搭建IOS自动化测试环境 一.win系统 1.1 安装虚拟机VM16 如果是macos系统请跳过 详细安装请看压缩包,私信可以提供安装包 1.2 VM解锁macOS 使用的是unlocker ...