实现一个TODO宏

简介:

实现一个TODO宏

实现一个能产生warning的TODO宏,用于在代码里做备忘,效果: 


下面一步步来实现这个宏。


Let’s do it

手动让编译器报警(报错)可以用以下几个方法: 

1
2
3
4
5
#warning sunnyxx
#error sunnyxx
#pragma message "sunnyxx"
#pragma GCC warning "sunnyxx"
#pragma GCC error "sunnyxx"

但我们知道,带#的预处理指令是无法被#define的。好在C99提供了一个_Pragma运算符可以把部分#pragma指令字符串化: 

1
2
3
4
5
#pragma message "sunnyxx"
// 等价于
_Pragma("message \"sunnyxx\"") // 需要注意双引号的转义
// 或
_Pragma("message(\"sunnyxx\")") // 需要注意双引号的转义

利用这个特性,我们就可以将warning定义成宏

1
2
3
4
5
#define SOME_WARNING _Pragma("message(\"报告大王!\")")
int main() {
    SOME_WARNING // [!]报告大王!
    return 1;
}

接下来,我们让这个宏能够接受入参,并显示到warning中去,这里会面临宏的基本用法的考验。 

1
2
#define STRINGIFY(S) #S
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))

个人认为不太可能在一个宏定义中完成这件事,需要用到辅助宏:STRINGIFY(S) 将入参转化成字符串,省去了_Pragma中全串加转义字符的困扰。
这时,一个基本功能的TODO宏就完成了,下面向其中加入额外的信息: 

1
2
3
4
5
6
7
8
// 两个已有的宏
#define STRINGIFY(S) #S
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))
// 延迟1次展开的宏
#define DEFER_STRINGIFY(S) STRINGIFY(S)
// 下面的宏在第一行用`\`折行
#define FORMATTED_MESSAGE(MSG) "[TODO-" DEFER_STRINGIFY(__COUNTER__) "] " MSG " \n"  \
    DEFER_STRINGIFY(__FILE__) " line " DEFER_STRINGIFY(__LINE__)

其中涉及到的知识: 

  • 两个常量字符串可以拼接成一个整串 “123””456” => “123456”
  • 使用到3个预定义宏__COUNTER__宏展开次数的计数器,全局唯一;__FILE__当前文件完整目录字符串;__LINE__在当前文件第几行
  • 在字符串中预定义宏应延时展开,如果将上面的DEFER_STRINGIFY换成STRINGIFY的话,如__LINE__就不能被正确展开成行数,而是成了一个常量字符串"__LINE__"
  • 为了美化,warning message中可以使用\n换行

于是,使用FORMATTED_MESSAGE(MSG)宏就可以将带文件路径、序号、行数等信息加入到最终的warning中。 


其实到这步已经OK了,为了让这个宏更加抢眼,还可以借鉴RAC,把宏定义成前面加@的形式:

1
#define KEYWORDIFY try {} @catch (...) {}

将最终的宏定义前面加上上面的宏后,使用时就可以加@前缀了(空的try-catch会被编译器优化,所以没啥性能损耗)


最终版本

1
2
3
4
5
6
7
8
#define STRINGIFY(S) #S
#define DEFER_STRINGIFY(S) STRINGIFY(S)
#define PRAGMA_MESSAGE(MSG) _Pragma(STRINGIFY(message(MSG)))
#define FORMATTED_MESSAGE(MSG) "[TODO-" DEFER_STRINGIFY(__COUNTER__) "] " MSG " \n" \
DEFER_STRINGIFY(__FILE__) " line " DEFER_STRINGIFY(__LINE__)
#define KEYWORDIFY try {} @catch (...) {}
// 最终使用下面的宏
#define TODO(MSG) KEYWORDIFY PRAGMA_MESSAGE(FORMATTED_MESSAGE(MSG))

What’s more

除此之外,还研究了半天如何在宏里面定义一个注释,这样就可以偷偷写// TODO: ...的注释,让Xcode导航栏中也出现这个TODO了:

但很可惜没有找到一个可行的方法,欢迎一起解决。
Xcode插件《XTodo》也是利用这个特性,可以尝试下。

如果需要一个产生error的宏,将这里替换成这样就好了:_Pragma(STRINGIFY(GCC error(MSG)))

同时,上面的代码在《github上》可以找到。也欢迎关注微博@我就叫Sunny怎么了一起交流。

References

http://clang.llvm.org/docs/UsersManual.html
https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html

目录
相关文章
|
5月前
|
存储 物联网 PyTorch
不用换显卡!大模型微调显存优化实操指南(附代码+效果对比)
不用换显卡!本文详解三大显存优化技巧:梯度检查点、混合精度训练、动态批量调整,附PyTorch实操代码与效果对比。16G显卡成功微调Llama 2 7B,显存占用直降38.5%,精度几乎无损,学生党、个人开发者也能轻松上手。
不用换显卡!大模型微调显存优化实操指南(附代码+效果对比)
|
6月前
|
传感器 人工智能 算法
智能之巅:AI 如何重塑第六代战斗机
近日,两架“歼-36”六代机在成都双机同框引发热议。这不仅是航空技术的突破,更标志着AI深度融入空战体系。从群体智能到“忠诚僚机”,AI正重塑战争逻辑,推动中国空军迈向“算法为王”的智能时代。
545 0
快捷键设置样式
【10月更文挑战第19天】快捷键设置样式
673 6
|
运维 Serverless 数据处理
函数计算产品使用问题之ComfyUI界面没有显示Manager按钮是什么原因
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
存储 Kubernetes 测试技术
企业级LLM推理部署新范式:基于ACK的DeepSeek蒸馏模型生产环境落地指南
企业级LLM推理部署新范式:基于ACK的DeepSeek蒸馏模型生产环境落地指南
872 12
|
存储 数据挖掘 BI
数据仓库深度解析与实时数仓应用案例探析
随着数据量的不断增长和数据应用的广泛深入,数据治理和隐私保护将成为数据仓库建设的重要议题。企业需要建立完善的数据治理体系,确保数据的准确性、一致性和完整性;同时加强隐私保护机制建设,确保敏感数据的安全性和合规性。
1267 55
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
753 0
|
存储 JavaScript 前端开发
箭头函数和普通函数在性能方面有什么区别
【10月更文挑战第27天】箭头函数和普通函数在性能方面各有特点,箭头函数在某些场景下具有一定的性能优势,但在实际开发中,不能仅仅为了追求性能而盲目地选择箭头函数或普通函数,应该根据具体的应用场景、代码可读性和可维护性等多方面因素综合考虑来选择合适的函数定义方式。
338 2
|
监控 安全 Linux
什么是蜜罐与蜜网(Honeypots and Honeynets)
【10月更文挑战第21天】蜜罐是一种用于网络安全的软件系统,充当入侵诱饵,通过引诱黑客攻击并监测其行为,帮助了解最新的攻击手段和漏洞。蜜罐分为高交互性和低交互性两种类型,前者提供全功能环境,后者仅模拟生产环境。多个蜜罐组成的网络称为蜜网,能够更全面地监测和分析入侵行为。使用蜜罐需谨慎,以免误报影响组织声誉。
797 1
|
监控 安全 网络安全
深入理解SNMP:网络管理的关键协议
【8月更文挑战第31天】
1097 1