C/C++如何写调试宏
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,表示这样的代码肯定不是做循环用的,它有什么用呢?
- 辅助定义复杂宏,避免宏替换出错
假如你定义一个这样宏,本意是调用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
C/C++如何写调试宏的更多相关文章
- 一道C语言面试题:写一个宏,将16位的整数转为Big Endian
题目:输入16位整数x,如0x1234,将其转为Big Endian格式再输出,此例为输出 0x3412 来源:某500强企业面试题目 思路:将x左移8位得到a,将x右移8位得到b,a+b即为所得 / ...
- preprocessor设置调试宏
调试宏:preprocessor设置 预处理器“调试”宏在Xcode项目模板的调试版本定义.预处理宏在编译时被解释和调试宏可以用来允许调试代码运行在调试版本中你的项目.如果你不确定你的项目已经确定,可 ...
- Linux内核调试方法总结之调试宏
本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...
- 在非MFC程序中使用调试宏 ASSERT(),VERIFY()和 TRACE()
游戏制作已经开始采用C++了,却鲜有人选择使用MFC.但笔者觉得的 ASSERT(),VERIFY()和 TRACE()这几个宏很好用.所以就想自己写一个版本来适应Windows平台下不同的工程类型. ...
- MFC程序中使用调试宏ASSERT()、ASSERT_VALID()、VERIFY()和TRACE()的区别
其实这篇文章说的很明白了:http://dev.gameres.com/Program/Other/DebugMacro.htm 结论如下: 1.ASSERT()测试它的参数,若参数为0,则中断执行并 ...
- 手把手教你写Windows 64位平台调试器
本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. ------------------------------------------------------- ...
- linux 内核(系统)、函数的理解、宏的程序调试
1.操作系统 1.1.Linux 内核(系统)的组成的部分: 内核主要有:进程调度.内存管理.虚拟文件系统.网络接口和进程通信五个部分组成. (1)进程调度 进程调度是CPU对多个进程对CPU访问的调 ...
- [置顶] 宏途_LCD调试流程.
今天在调试宏途的LCD屏时,开始是开机屏幕不亮,背光都不亮,可能板子已经损坏,一般通过测试电流电压简单验证,(注:硬件引脚没焊好也会引起读lcd id出现错误!!!)出现这个问题一般是因为引脚没焊好, ...
- masm中list文件和宏的一些常用编译调试查看方法
我们知道使用用 ml /Fl a.asm 可以生成lst文件,但是如果不加调整,masm默认生成的lst文件是非常大的,因为它包含了很大的windows必须用到的头文件内容,为了减小lst文件大小,便 ...
- python flask框架学习(三)——豆瓣微信小程序案例(一)templates的使用,宏的使用,前端后台传数据,前端写python语句
目录 一.templates的使用 (1)在templates里创建一个index.html (2)再在app.py里写 (3)展示效果 二.构建第一个电影评分 (1)准备好素材放进static里的i ...
随机推荐
- Web Audio API 第3章 音量和响度
此章介绍的科普物理声音知识相当有用,编程的反而涉及的少 音量和响度 Loudness 响度 注:根据<韦氏词典>,响度是"一种声音的属性,它决定了所产生的听觉感觉的大小,主要取决 ...
- Scala数值类型转换、算数运算符、关系(比较)运算符和逻辑运算符
原则 强制类型转换 Java : int num = (int)2.5Scala : var num : Int = 2.7.toInt 数值类型和String类型之间的转换 (1)基本类型转 Str ...
- #根号分治,树上倍增#洛谷 3591 [POI2015]ODW
题目 分析 考虑直接用倍增跳会TLE,设\(f[x][i]\)表示以\(x\)为起点每次跳\(i\)步的点权和, 这可以预处理出来,综合一下两种做法,当\(i>\sqrt{n}\)时直接上倍增, ...
- Avalonia的UI组件
Avalonia是一个强大的跨平台UI框架,允许开发者构建丰富的桌面应用程序. 它提供了众多UI组件.灵活的布局系统.可定制的样式以及事件处理机制. 在这篇博客中,我们将详细解析Avalonia的UI ...
- OpenHarmony中的HDF单链表及其迭代器
概念 为了性能考虑,嵌入式系统一般使用C语言进行开发,由于C语言标准库没有封装链表,所以嵌入式系统一般自己设计和实现链表这种数据结构.单链表是链表中的一种,本文描述OpenAtom OpenHarmo ...
- vc修改ocx控件classID
背景: 客户希望,classID 为客户自已定义好的一个 ID ,由于我们自动生成的 classID 和客户定义的 ID 不同,所以我们需要修改 classID 为客户指定的 ID 解决方法: 1.打 ...
- k8s 深入篇———— docker 镜像是什么[二]
前言 简单介绍一下docker的镜像. 正文 前面讲到了容器的工作原理了(namespace 限制了时间, cgroup限制了资源),知道docker 历史的也知道,docker 之所以能够称为容器大 ...
- c# 反编译对比(旧)
前言 旧的都是我以前博客的迁移. 我们写代码有时候遇到一些问题,或者我们想优化我们的代码,我们想要看编译后的运行情况,那么反编译是必须要做的一件事. 正文 在此我自己使用的是reflector和ILS ...
- mysql 重新整理——sql 执行语句的顺序[五]
前言 盗图: 其实在复杂的语句中,需要我们逐步去分析,然后呢,我们了解一些优化器到底是如何帮我们优化的,就知道到底是mysql怎么执行代码. 我把以前的丢了,后续补全.
- InnoDB常用锁总结(行锁、间隙锁、临键锁、表锁)
相关文章 数据库系列:MySQL慢查询分析和性能优化 数据库系列:MySQL索引优化总结(综合版) 数据库系列:高并发下的数据字段变更 数据库系列:覆盖索引和规避回表 数据库系列:数据库高可用及无损扩 ...