[编程手记小技巧]自定义调试信息宏及gcc技巧 收藏
文中所讲的函数,可能是指使用宏定义的“宏函数”,也可能是指真正的函数。假设你不会混淆它们,也假设你能理解它们。
许多开源项目代码中都有自定义的调试信息的相关的函数。比如u-boot中(来自common.h头文件)
- #ifdef DEBUG
- #define debug(fmt,args...) printf (fmt ,##args)
- #define debugX(level,fmt,args...) if (DEBUG>=level) printf(fmt,##args);
- #else
- #define debug(fmt,args...)
- #define debugX(level,fmt,args...)
- #endif /* DEBUG */
如果定义了DEBUG这个宏,则使用debug会输出自定义的信息,如果没有定义,则什么也不会输出——不执行任何语句。
这种调试函数有N种形式,比如
- #ifdef DEBUG
- #define debug_msg(format,...) /
- fprintf(stdout,format,##__VA_ARGS__)
- #else
- #define debug_msg(format,...)
- #endif /* DEBUG */
我们要注意##这个符号的用法,它用于连接##前后的字符串,在debug_msg只有一个参数情况下,它的作用就显示出来了。它属于C/C++的范畴,而不是特定编译器的。
一个带详细信息的出错提示
- #define print_error(str) /
- do { /
- fprintf(stderr, "Oh God!/nFile:%s Line:%d Function:%s:/n" ,/
- __FILE__, __LINE__, __func__); /
- perror(str); /
- return (EXIT_FAILURE); /
- } while (0)
它不仅打印导致出错的文件、行号、函数,还打印这个出错的原因。EXIT_FAILURE在系统中定义为1。当然,也可以使用exit(1),等等。
这里要注意一点,在linux中有一些习惯的约定,比如表示判断的函数,返回非零表示成功,返回0表示失败;表示操作性质的函数,返回0表示操作成功,返回非零表示操作失败。——经典地,strcmp这个函数,如果要比较的两个字符串相同,它是返回0的。
这里我们同样要注意一点,输出调试信息与出错信息是两回事,调试信息是帮助我们更好地理解程序的运行、各个变量或程序的走向,这些信息是可有可无 的。但是出错信息却不同,程序已经出错了,没理由也不能继续运行下去了。比如给某一指针分配内存空间,如果分配失败,我们必须返回或退出函数(有些面试题 会考这个的,山人见过)。这些是我们需要注意的。
上面说到可有可无,怎么实现?很简单,就是在你使用到的地方定义那个宏就行了(比如上面的例子中是DEBUG)。在u-boot中,如果在公共头文 件common.h中打开DEBUG宏,则所有调用debug的函数都会打印调试信息,这习惯可不好。因此,我们在需要的地方才打开调试宏。
此外,也可以利用gcc编译器的-D选项来打开调试宏。使用也简单,直接在-D后面添加调试宏就行了。比如
$ gcc -DDEBUG -g -o foo foo.c
当然,实际例子不会这么用的。
下面举一个山人在实践中的例子。
假设的背景:一个摄像头,在ARM开发板中的设备文件是“/dev/video1”,在PC平台中却是“/dev/video”。
是不是必须使用两套代码呢?没必要。我们可以在代码中这样定义
- #ifdef __ARM__
- #define device "/dev/video1"
- #else
- #define device "/dev/video"
- #endif
这样,我们可以在Makefile中指定CFLAGS为-D__ARM__,代码就不用进行修改了。这在开发、调试阶段很有用,在后面的阶段则可以不使用这种方法。
当然,解决问题的方法可以有多种,比如使用两个Makefile文件(当然,文件名不能相同,而且,编译器也需要改变),编译时,使用-f指定Makefile文件。
上面只是讲了一种方法,方法没有绝对的好与不好,找到合适自己的才是最好的。
木草山人