C语言使用宏输出调试信息实战
如何使用宏定义输出信息
C语言提供了#, ##, __VA_ARGS__等符号来帮助我们在宏定义中更好地输出信息。使用方式如下:
输出如下:
#的作用是,它会在所引用的宏变量左右两边加一对引号。所以FUN1中的printf函数不会出错,因为FUN1(hello)预编译时被替换成了printf("hello\n")
##的作用是,把两个宏变量连接成一个,FUN2(1, 2)预编译时被替换成了printf("The num is %d\n", 12)
支持c99标准的编译器允许宏像普通函数一样,可以实现可变参数。方法就是把参数列表写成"..."。然后在宏函数中使用__VA_ARGS__引用所有参数。在调用FUN3时传了2个参数,预编译时被替换成了printf("The author is %d years old\n", 23)
FUN4和FUN3的效果一样,那为什么宏函数的参数要写成"format, ...", 而且在使用__VA_ARGS__的时候前面加上##?原因是这么写可以强制使用宏的用户必须输入一个以上的参数。如果无参调用FUN4(),编译就会出错。##__VA_ARGS__中两个#号的作用是,当可变参数为空时,让编译器忽略前面那个逗号。比如调用FUN4("hello\n")时,被替换成printf("hello\n"), 而不是printf("hello\n", )
为什么要使用宏定义输出信息
我们为什么不直接在代码中使用printf函数输出信息,而非得在外面包装一层宏,然后调用宏函数来输出?这不多此一举嘛。。。
在一份源代码中,我们编写代码是一个环节,提供给用户使用是另一个环节。在我们编写代码的环节中,需要输出一些调试信息来辅助我们确认程序逻辑的正确性。但是提供给用户使用的时候,用户又不想看到这些调试信息。假设我们在编码环节直接使用printf在代码中输出调试信息,那么提交给用户的时候,又需要手动把这些printf语句一条一条注释掉。如果用户使用了一段时间,发现一个bug,要求我们把这个bug fix掉。那么为了能够在fix bug的时候能够调试,我们又需要手动地把这些printf语句的注释再一条条去掉。。。可以看到,很多时间都浪费在了这些琐事上,这就直接造成了开发效率低下的结果。假如我们又一个类似于开关的东西,在我们调试的时候,把开关打开,让这些printf语句可以正常输出。在提交给用户使用的时候,把开关关掉,阻止这些printf语句的输出。而不需要手动去改代码,这样维护这套代码耗费的精力就会少很多。这样话题就回到了标题,为什么要使用宏定义输出信息?因为宏定义可以巧妙地帮我们解决刚才说的问题。
假设我们写了这样一份代码,方案一:
运行,输出如下
可以看到,第二条printf可以正常输出。
当我们交给用户使用的时候,可以直接把 #define __DEBUG 这一行删掉,再次运行,可以看到下面的效果:
so。。如果这里面所有的调试信息都是这么输出的,在我们交给用户的时候,只需要删掉一行代码就可以关掉调试信息的输出。当我们需要fix bug的时候,也只需要加一行代码就可以开启所有的调试输出。
这个方案看起来很不错,极大地促进了码农的生产力。但是还有问题,就是这份代码编写的时候原来的一条printf语句现在需要3行代码去完成,而且编写好的代码也看起来很复杂,给阅读代码的人增加了潜在的思维包袱。所以使用第一部分介绍到的红函数,现在提出方案二:
这样写的话,在我们需要输出调试的地方直接使用DEBUGPRINT宏函数就好。开启和禁止调试信息的输出和上面的方法操作是一样方便的。而且编写简单,代码也看着更加美观。
方案二已经基本满足我们的需求了,但是美中不足。因为我们调试程序的时候,经常希望看到这条调试信息是哪个文件,那一行输出的。这种问题C语言编译器为我们提供了解决方案。C语言编译器定义了以下内置宏__LINE__, __FILE__, 使用过其他语言的朋友应该一眼就看出来这两个宏是干嘛用的了,分别表示所在行和所在文件,编译器在编译时,会把这些宏替换成相应的内容。基于这个,我们提出方案三:
输出