标签 宏 下的文章

多行宏定义中的注释问题

早上在写代码时遇到这样一个问题:即如何在一个拥有多行的宏定义中做注释?,这里把方法演化的过程贴出来,可能对某些朋友有些借鉴意义。

宏定义高深莫测,而且是比较细节的东西,详细说明请参见"C参考手册"之类的书籍。

在我的代码中,我大致要做这样一个简单的事情:printf("%s%s%s\n", "hello", "macro", "yeah!"); "%s%s%s\n"这个字符串中每一项输出都有一定的含义,而且在真实代码里,这个串中的输出项可不止3个,所以一个直接的想法就是将其定义为一个宏。

#define STR_OUTPUT_FORMAT_V0  "%s%s%s\n"
printf(STR_OUTPUT_FORMAT_V0, "hello ", "macro, ", "yeah!");
程序输出:hello macro, yeah!

由于真实代码中这个串很长,所以打算美化一下格式,定义成下面的样子:
#define STR_OUTPUT_FORMAT_V1  "%s\
                               %s\
                               %s\n"
printf(STR_OUTPUT_FORMAT_V1, "hello ", "macro, ", "yeah!");
程序输出:hello                                macro,                                yeah!

这样的定义显然不对,也在我意料之中,续行符将空格也续到格式串中了,导致输出的结果中带有大量空格。

改进一下,利用C语言的字符串自动连接语法。
#define STR_OUTPUT_FORMAT_V2  "%s"\
                              "%s"\
                              "%s\n"
printf(STR_OUTPUT_FORMAT_V2, "hello ", "macro, ", "yeah!");
程序输出:hello macro, yeah!

现在的问题是如何在这样一个多行的宏定义里加入注释,字段含义特殊,加上注释有利于以后维护以及别人阅读你的代码,否则一堆%s%s,让人看了就头痛。先这么加试试:
#define STR_OUTPUT_FORMAT_E1  "%s"\   /* comment1 */
                              "%s"\   /* comment2 */
                              "%s\n"  /* comment3 */
printf(STR_OUTPUT_FORMAT_E1, "hello ", "macro, ", "yeah!");
我们得到的结果:编译错误。
通过gcc -E 选项我们看到,宏替换后的代码:
                              "%s"\
                              "%s\n"
int main() {
        printf("%s"\, "hello ", "macro, ", "yeah!");
}

由于没有续行符在注释前面,所以宏定义的后两行实际上并没有被包含在宏定义中,就像没有暂住证的人一样,被GCC这个"警察"逮个正着。

继续改进:
#define STR_OUTPUT_FORMAT_V3  "%s"   /* comment1 */ \
                              "%s"   /* comment2 */ \
                              "%s\n"  /* comment3 */
printf(STR_OUTPUT_FORMAT_V3, "hello ", "macro, ", "yeah!");
程序输出:hello macro, yeah!
显然预编译器忽略宏定义中的注释以及空格,STR_OUTPUT_FORMAT_V3就完全符合我的要求了。

当然,很多人不建议使用宏,特别是C++的Fans,宏也的确有很多弊端,这里也有替代方法:
const char *str_output_format = "%s"    /* comment1 */
                                 "%s"    /* comment2 */
                                 "%s\n"; /* comment3 */
printf(str_output_format, "hello ", "macro, ", "yeah!");
程序输出:hello macro, yeah!

用一个字符串变量代替格式宏,还可以避免上述由于在宏中做注释带来的一系列问题。

你提供默认选项了吗

五一期间到姥姥家串门儿,自然午饭要在那吃,可中午做饭时听姥姥抱怨新买的电饭煲做饭时间太长而且还夹生,我好奇的走了过去想看看究竟,电饭煲的确是新买不久的,而且是美的的,我心想大牌厂商应该不会有这样的质量问题呀,一定是姥姥使用上的问题。仔细看了看控制板,果然不出所料,电饭煲的’功能选择’键停在了功能档的中央,既不是左边的’煮饭’也不是右边的’煲粥’,遂告诉姥姥以后煮饭要把功能选择调到’煮饭’档。

吃完午饭,闲来无事,便又想起这件事来,联系到Keso写过的一篇叫’默认的力量‘的文章,自己又有了些新想法。当Keso那篇文章说的是IE用’默认的力量’打败了’Netscape’,其中总结的一个潜规则就是’用户都是无知者’,’无知者’一方面可能没有能力去挖掘你的工具提供的诸多强大的功能,另一方面则是少有人愿意为挖掘出那些强大的功能而付出劳动,能用就好,最理想的就是’拿来即用’。这也好比一台理想的电视机,从商店买回家里,插上电源,按下电源开关,全国各个电视台就都能在电视上看到,而无需去人工搜索设定电视频道。这样的情形在很多场合都能见到,在软件开发中也不例外。

自己曾经在一个项目中提供了这样一组宏,其目的就是为了利用编译开关来选择程序输出的调试日志级别,比如level 1级别的日志只是输出某些最重要的调试信息,level 3级别的则是输出所有调试信息。当时没有多想就在一个头文件中写下了如下的代码:
/*
 * 代码片断1
 */
#ifdef _DEBUG_LEVEL1

#define MY_TRACE_L1(name, stat) TRACE(name, stat)
#define MY_TRACE_L2(name, stat) {}
#define MY_TRACE_L3(name, stat) {}

#endif /* _DEBUG_LEVEL1 */

#ifdef _DEBUG_LEVEL2

#define MY_TRACE_L1(name, stat) TRACE(name, stat)
#define MY_TRACE_L2(name, stat) TRACE(name, stat)
#define MY_TRACE_L3(name, stat) {}

#ifdef _DEBUG_LEVEL3

#define MY_TRACE_L1(name, stat) TRACE(name, stat)
#define MY_TRACE_L2(name, stat) TRACE(name, stat)
#define MY_TRACE_L3(name, stat) TRACE(name, stat)

不久一同项目组的哥们发来mail说程序编译不过去了,我迅速查了一下,发现在Makefile中没有定义任何DEBUG LEVEL宏,再看一下上面的代码你就会知道如果没有定义任何DEBUG LEVEL宏,那么用在程序代码中的MY_TRACE_Ln就是’undefined symbols’,程序自然不能编译通过。解决的方法很简单:添加一个DEBUG LEVEL宏,不过这样并不是一劳永逸的做法。正如我们上面所言,’用户都是无知的’,使用该程序的人最愿意看到的就是敲入’make’,马上得到可执行程序,而不是先添加一个宏定义后再make。最后我提供了一个默认的DEBUG LEVEL,解决方法如下:
/*
 * 代码片断2
 */
#if !defined(_DEBUG_LEVEL1) && !defined(_DEBUG_LEVEL2) && !defined(_DEBUG_LEVEL3)
#define _DEBUG_LEVEL1
#endif
把’代码片断2′放到’代码片断1′的前面,这样即使用户没有定义任何DEBUG LEVEL宏,程序仍然可以顺利通过编译。这段小插曲提醒我们时刻想着提供一个’默认的选项’,有些时候它事关重大,不能小觑它的力量。

仅仅提供一个’默认选项’就够了么?我们再回到前面的那个’美的’电饭煲的设计上。从功能上该电饭煲提供了两种功能’煮饭’和’煲粥’,但是它好像并未提供默认的选项,而是将’功能选择’键放在了功能档的中央。也许有些人会说这可能就是电饭煲提供的默认的选项呢,也许这个中间档是出于安全考虑呢。但是我要说即使它是默认选项,它也有误导的作用,不了解它的人会认为这样就可以煮饭了,我姥姥就是这么想的。没错儿,这里我要说的就是’谨慎选择默认项,不要误导使用者’。这里我没有很好的软件开发中的例子来说明,似乎少了些说服力,不过通过这个电饭煲的例子你也可以悟到一些东西。其实如果我是电饭煲的设计者,我是不会允许将’功能选择’键放在了功能档的中央的,要么放在’煮饭’档要么放在’煲粥’档。^_^

你在开发中提供默认选项了么,如果还没有的话,那就该好好想想是否是时候添加一个没有误导作用的默认选项了。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats