可变参数宏

参考一
参考二
参考三
参考四

一、 format、...、__VA_ARGS__、args、stdout
二、 自适应参数——##
三、 MTK中的用法,以及__FILE__、__LINE__、__FUNCTION__
四、 上机实践
五、 总结
1、 理论+实践=学好编程
六、 整体性学习
1、 获取信息
1. 压缩:format,...,##,__VA_ARGS__
2. 容量:基本宏+函数特性+调试技巧(资源浪费)
3. 速度:在函数中使用format和...,结合宏参数实现可变参数的使用
2、 理解
字面意思:参数可变,format自动适应参数类型/格式
3、 拓展
1. 背景: 编译原理
2. 横向: main的参数;可变参数函数的实现;断言;__LINE__等宏参数
3. 纵向: 太极拳中,拳义理是不变的,招式是可变的;射箭中,背肌撒放的原理是不变的,但是撒放的收发是可变的;内核链表中,指针的存储和指向是不变的,但是指针至上的数据区是可以存放任意类型的数据。
4、 纠错
宏的本质是替换,这点任何时候都不能变/忘
参数可变,要结合函数本身的特性,不能牛唇不对马嘴
可变参数宏的写法很灵活,并不是只能使用fprintf,或者printf
参数列表可以为空,但是需要##
宏可以为空宏
5、 应用
可以使用这个编写自己的printf函数,加上宏开关语句,可以很方便的在调试和发布之前切换,而不需要自己手动评比调试语句
6、 测试
IFACE300代码中使用
#ifdef判断的是后面的宏有没有宏定义,而不论该宏定义为0还是非0
结构
模型
高速公路

一、 format...__VA_ARGS__argsstdout

printf()fprintf()这些输出函数的参数是可变的,在调试程序时,你可能希望定义自己的参数可变的输出函数,
那么可变参数宏会是一个选择。
C99中规定宏可以像函数一样带有可变参数,比如
#define LOG(format, ...) fprintf(stdout, format, __VA_ARGS__)
GCC中同时支持如下的形式
#define LOG(format, args...) fprintf(stdout, format, args)

其用法和上面的基本一致,只是参数符号有变化
代码解析:

  • ...代表参数可变
  • format单词是格式的意思,实际操作中会自动根据参数的类型选择像样的格式进行输出
  • stdoutfprintf函数的第一个参数,表示将数据输入到的地方是标准输出台
  • __VA_ARGS__是编译预处理中会被实际输入的参数内容所替代

同上,可以推理出用printf做可变参数宏的写法:
#define LOG(format, ...) printf(format, __VA_ARGS__)

二、 自适应参数——##

有一点需要注意,上述的宏定义不能省略可变参数,尽管你可以传递一个空参数,这里有必要提到##连接符号的用法。
##的作用是对token进行连接,在上例中,format、__VA_ARGS__、args即是token
如果token为空,那么不进行连接,所以允许省略可变参数(__VA_ARGS__args),对上述变参宏做如下修改
#define LOG(format, ...) fprintf(stdout, format, ##__VA_ARGS__)
#define LOG(format, args...) fprintf(stdout, format, ##args)
#define LOG(format, ...) printf(format, ##__VA_ARGS__)

上述的变参宏定义不仅能自定义输出格式,而且配合#ifdef #else #endif在输出管理上也很方便,
比如调试时输出调试信息,正式发布时则不输出,可以这样

  1. #ifdef DEBUG
  2. #define LOG(format, ...) fprintf(stdout, ">> "format"\n", ##__VA_ARGS__)
  3. #else
  4. #define LOG(format, ...)
  5. #endif

在调试环境下,LOG宏是一个变参输出宏,以自定义的格式输出;
在发布环境下,LOG宏是一个空宏,不做任何事情。
这里引用一下MTK中TRACE调试的讲解,了解为什么下面的宏要定义为空:

种种迹象和从理论上看来,TRACE和MMI_ASSERT是调试的好帮手,但在发布软件时,带上了这个会引来不必要的麻烦.MMI_ASSERT增加了系统重启的频率.TRACE增加了系统的ROM,RAM和CPU的开销.在工作中,我们曾经发现一款手机,由于ROM过于紧张,添加几条TRACE就会出现编译错误,去掉TRACE就编译通过了,导致出了BUG调试十分的麻烦.如何写一种使用时可以TRACE错误,不使用时又不占用系统资源的TRACE呢,我见许多人这样处理,因为NULL会被编译器优化点,后面括号变成一个表达式了.表达式对系统的开销自然小于函数了.

  1. #ifdef DEBUG_KAL
  2. #define MYTRACE(...) kal_prompt_trace(MOD_WAP, __VA_ARGS__)
  3. #else
  4. #define MYTRACE NULL
  5. #endif

三、 MTK中的用法,以及__FILE____LINE____FUNCTION__

自定义调试信息的输出(编者语:应该是MTK编程中的写法,做个参考就行)

  1. #define CMMB_LOG(format, ...) mmi_trace("[CMMB] "format" File:%s, Line:%d, Func:%s" , ##__VA_ARGS__, __FILE__, __LINE__ , __FUNCTION__);
  2. #define CMMB_LOG(type, fmt, args...) __android_log_print(type, LOG_TAG, fmt, args)
  3. #define CMMB_INF(fmt, ...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "[%s](%d): " fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__)
  4. #define CMMB_ERR(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "[%s](%d): " fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__)
  5. #define CMMB_WRN(fmt, ...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "[%s](%d): " fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__)
  1. __VA_ARGS__是一个可变参数的宏,宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错。
  2. __FILE__ 宏在预编译时会替换成当前的源文件名
  3. __LINE__宏在预编译时会替换成当前的行号
  4. __FUNCTION__宏在预编译时会替换成当前的函数名称

四、 上机实践

系统Ubuntu 16.04GCC 5.4.0
记录:

  • 是fprintf,不是printf
  • 每个参数都要带"",所以参数没办法是变量暂时没发现,有待验证
  • format不能带""
  • 参数之间以""间隔
  • 除了__VA_ARGS__宏之外,还可以使用其他的宏,如__FILE__, __LINE__, __FUNCTION__
  1. #include <stdio.h>
  2. #define LOG(format, ...) printf("fomat, File:%s, Line:%d, Func:%s\n", ##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__)
  3. #define LOG1(format, ...) fprintf(stdout, format, ##__VA_ARGS__)
  4. #define LOG2(format, ...) printf(format, ##__VA_ARGS__)
  5. #define LOG3(format, ...) printf(format "%s %d %s\n", ##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__)
  6. int main(void)
  7. {
  8. int a = 123;
  9. LOG1("a" "\n");
  10. LOG2("b" "\n");
  11. LOG3("c" "\n");
  12. return 0;
  13. }


输出结果:

五、 总结

1、 理论+实践=学好编程

开发中会遇到一些新问题或者说已经模糊了的旧知识,对于新问题我们一般都是百度,看到讲解明白后就行了,顶多就是总结记录一下,但是实际效果并不明显,过不了多久遇到同样的问题,还是要百度查阅资料,这也可能是因为我记忆力不怎么出众的原因,不能一概而论。但是有这个问题的应该不少,我想想觉得问题的关键就是:到目前为止,所做的工作只单纯的设计到理论、概念,并没有亲自动手按照教程进行实践操作。
就像《如何高效学习》中提到的:只是获取了信息,还没有应用,更没有纠错,知识怎么能记得牢、用的好、正确无误呢?

=====================================随笔部分=================================================

六、 整体性学习

1、 获取信息

1. 压缩:format...##__VA_ARGS__

2. 容量:基本宏+函数特性+调试技巧(资源浪费)

3. 速度:在函数中使用format...,结合宏参数实现可变参数的使用

2、 理解

字面意思:参数可变,format自动适应参数类型/格式

3、 拓展

1. 背景: 编译原理

2. 横向: main的参数;可变参数函数的实现;断言;__LINE__等宏参数

3. 纵向: 太极拳中,拳义理是不变的,招式是可变的;射箭中,背肌撒放的原理是不变的,但是撒放的收发是可变的;内核链表中,指针的存储和指向是不变的,但是指针至上的数据区是可以存放任意类型的数据。

4、 纠错

  • 宏的本质是替换,这点任何时候都不能变/忘

  • 参数可变,要结合函数本身的特性,不能牛唇不对马嘴

  • 可变参数宏的写法很灵活,并不是只能使用fprintf,或者printf

  • 参数列表可以为空,但是需要##

  • 宏可以为空宏

5、 应用

  • 可以使用这个编写自己的printf函数,加上宏开关语句,可以很方便的在调试和发布之前切换,而不需要自己手动评比调试语句

6、 测试

  • IFACE300代码中使用

  • #ifdef判断的是后面的宏有没有宏定义,而不论该宏定义为0还是非0

结构

模型

高速公路





posted @ 2016-12-13 21:03  刘一卜  阅读(1380)  评论(1编辑  收藏  举报