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++ 线程安全的队列

    无界队列 #include<queue> #include<mutex> #include<condition_variable> #include<opti ...

  2. 使用OpenMP与AVX优化矩阵乘法

    使用OpenMP与AVX优化矩阵乘法 由于课设内容做的太过简(mo)单(yu),于是在去年12月初的时候就计划写三篇博客随笔作为实验报告,前两篇简单介绍了OpenMP和SIMD指令进行铺垫,本篇将会介 ...

  3. #树状数组,dp#SGU 521 North-East

    题目 在平面上有 \(n\) 个点,现在有一个人要从某个点出发, 每次只能到达横纵坐标都超过原坐标的点,也就是 \(x_j<x_i,y_j<y_i\) 如果他要经过最多的点,那么哪些点是可 ...

  4. 如何实现OpenHarmony的OTA升级

    OTA简介 随着设备系统日新月异,用户如何及时获取系统的更新,体验新版本带来的新的体验,以及提升系统的稳定性和安全性成为了每个厂商都面临的严峻问题.OTA(Over the Air)提供对设备远程升级 ...

  5. HarmonyOS传感器开发指南

      HarmonyOS系统传感器是应用访问底层硬件传感器的一种设备抽象概念.开发者根据传感器提供的Sensor接口,可以查询设备上的传感器,订阅传感器数据,并根据传感器数据定制相应的算法开发各类应用, ...

  6. 用HarmonyOS做一个可以手势控制的电子相册应用(ArkTS)

    介绍 本篇 Codelab 介绍了如何实现一个简单的电子相册应用,主要功能包括: 1.  实现首页顶部的轮播效果. 2.  实现页面多种布局方式. 3.  实现通过手势控制图片的放大.缩小.左右滑动查 ...

  7. redis 简单整理——缓存设计[三十二]

    前言 简单整理一下缓存设计. 正文 缓存的好处: ·加速读写:因为缓存通常都是全内存的(例如Redis.Memcache),而 存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效 地 ...

  8. 实训篇-Html-表格练习2

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. 获取电脑真实的IP地址,忽略虚拟机等IP地址的干扰

    /** * @author yins * @date 2018年8月12日下午9:53:58 */ import java.net.Inet4Address; import java.net.Inet ...

  10. next.js app目录 i18n国际化简单实现

    最近在用next写一个多语言的项目,找了好久没找到简单实现的教程,实践起来感觉都比较复杂,最后终于是在官方文档找到了,结合网上找到的代码demo,终于实现了,在这里简单总结一下. 此教程适用于比较简单 ...