C语言应用系列: 01. 宏在项目中的应用
来源:互联网 发布:cst仿真软件书籍 编辑:程序博客网 时间:2024/05/09 04:15
C语言中的宏是C编译器提供的预处理命令的一种,在预处理期间(编译前)进行求值替换。善用宏可以让代码准确(SPOT原则)、高效(编译期求值)、易维护(容易理解),配合编译器本身提供的一些机制,可以在不破坏程序完整性的前提下,快捷方便的对程序执行调试(打开/关闭调试信息输出)。但是如果错误应用或者滥用宏,也会导致晦涩的错误、让代码难以维护。本文对C语言中宏的应用做出详细的介绍,并就具体项目应用做分析,最后,提供项目级的宏调试代码文件。
1. 宏的形式
分为两种形式。在一个宏定义中,宏的名称紧跟着一个左圆括弧,则称之为函数式宏;反之,则为对象式宏。
(1) 对象式宏:由宏名及可选的宏体构成。
#define name sequence-of-tokens(optional)
例如:
#define HTTP_REQUEST_SIZE 0x1024 /* http request packet size limitted */
#define MAX_CONCURRENT 5000 /* maximum value of server concurrent */
一般代码中大量存在相关宏的引用,当相关宏值需要更改时,只需要更改宏定义,而不用跟踪整个代码来替换。
(2) 函数式宏:由宏名、紧接的左圆括弧及可选任意参数、右圆括弧及可选宏体构成。
#define name( identifier-list(optional) ) sequence-of-tokens(optional)
例如:
#define sum( x , y ) ( (x) + (y) )
#define tcp_socket() socket(PF_UNIX,SOCK_STREAM,0)
#define debug_printf(...) printf(__VA_ARGS__)
需要注意的是,左圆括弧必须紧接着宏名,否则,左圆括弧连同右边所有部分,会成为宏体,例如,编写如下代码段:
/* test01.c */
#define multiply ( x, y ) ( (x) * (y) )
int i = multiply(x,y);
使用 gcc 按照如下指令编译:
$ gcc -E macro.c
可以看到输出为:
# 1 "macro.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "macro.c"
int i = ( x, y ) ( (x) * (y) )(x,y);
而不是我们期望的:
int i = ( (x) * (y) );
2. 宏的重新扫描
宏调用在扩展(替换)以后,会从替换宏体的开始处重新开始宏处理,以实现深层替换。宏的所有替换不是发生在宏定义的地方,而是全部发生在宏调用的地方。例如,如下代码段:
#define plus(x,y) add(y,x)
#define add(x,y) ((x)+(y))
plus(plus(a,b),c)
其扩展过程分布描述如下:
Step Result
1. original plus(plus(a,b),c)
2. add(c, plus(a,b))
3. ((c)+(plus(a,b)))
4. ((c)+(add(b,a)))
5. final ((c)+(((b)+(a))))
关于重新扫描有很重要的一点:C标准明确规定,宏体内直接或间接的出现宏(宏名)本身不会再度扩展。
我们考虑某平台下存在函数:
void func(int x);
并且代码中大量存在func的调用,假设现在另一平台下相同功能的函数:
void func(int x, int y);
则可以定义如下宏实现:
#define CONSTANT_VALUE 0
#define func(x) func(x,CONSTANT_VALUE)
再考虑另外一种情况:
#define char unsigned char
以上宏期望代码中全部的 char 型别用 unsigned char 替换。如果允许再度扩展,则陷入无限循环。
很可惜,以上限制也给我们的编码带来了不利。例如如下代码段是达不到期望值的:
#define factorial(x) ( ( 0 == (x) ) ? 1 : factorial(x-1) )
int iFac = factorial(100);
以上代码段我们期望实现编译期100的阶乘求值。很好的设计思路,很可惜,标准不允许。事实上,上行语句的扩展是:
int iFac =( ( 0 == (100) ) ? 1 : factorial(100 -1) );
很显然,程序会通过预处理和编译,但是在链接时候报错,除非正好存在函数 factorial(type x)(type是参数型别,要求支持从int到type的正确转换)。
附带描述一句:C++ 编译器亦不支持以上宏,但是C++中模板(template)可实现以上功能
3. 预定义的宏
C标准预定义了一些宏,日常编码及调试中非常有用。就日常经常用到,描述如下:
__LINE__ 代码当前行
__FILE__ 代码所在文件
__func__ 代码所在函数
__DATE__ 代码编译时日期
__TIME__ 代码编译时时间
4. 边界效应
对于函数式宏而言,其与函数的差别是:函数的实参只求值一次,而函数式宏可能求值多次导致边界效应。考虑如下代码段:
#define SQUARE(x) ( (x)*(x) )
int square(int x) { return x*x; }
int iW;
iW = 3;
int iSM = SQUARE(iW++);
iW = 3;
int iSF = square(iW++);
宏调用行扩展后为:
int iSM = ( (iW++)*(iW++) );
此表达式的结果C语言未定义(因为C标准规定参数求值顺序是编译器自由实现的,另:任何在一个表达式里读取两次变量值并且对变量操作的,结果均未定义)。但是很明显,和函数调用造成的后果不同(可能返回值一样,但是对于iW的结果值一定不同)
5. 不定参数
C语言中可以用 ... 表示不确定个数的参数。
#define name( identifier-list, ... ) sequence-of-tokens(optional)
#define name( ... ) sequence-of-tokens(optional)
在宏体中,__VA_ARGS__ 可用来代替不定参数,并且 __VA_ARGS__ 也只可以出现在具有不定参数(...)的宏中。本文的最后会有项目级的不定参数的应用案例。此处给出如下简单例子:
#ifdef OPEN_DEBUG
#define dprintf(format, ...) printf(format, ##__VA_ARGS__)
#else
#define dprintf(format, ...)
#endif
此处需要说明的另外一点是:粘贴符和逗号的消除问题。如前定义,当宏 OPEN_DEBUG被定义时,实际函数调用时 printf。此处存在的问题是,当不定参数个数为0时(printf中是允许的),其后的逗号(format后面的逗号)会导致编译失败,因此需要在 __VA_ARGS__前面添加粘贴符(##),当不定参数为空时,粘贴符(##)会去掉其前面的逗号,从而避免该问题出现。我们也可用如下代码进行测试:
/* test02.c */
#define dprintf(format,...) printf(format, ##__VA_ARGS__)
dprintf("comma is disappeared ?/n");
dprintf("comma is appeared %s/n", "let comma appear");
可以用 gcc -E test02.c 看输出:
printf("comma is disappeared ?/n");
printf("comma is appeared %s/n", "let comma appear");
6. 其他
取消宏定义:#undef
检测是(否)定义:#if(n)def ... #else ... #endif
字符串化:#
粘贴符:##
7. 实际项目中用来做输出调试的代码
/* dstdio.h */
/* dstdio.c */
/* test.c */
对于以上代码,测试方法如下(gcc compiler)
A. 不定义 OPEN_DEBUG:
gcc -o test test.c dstdio.c
此时函数 output_printf 调用输出;如下:
15/09/2009, 15:38:24; test.c:0005(main) test begin
15/09/2009, 15:38:24; test.c:0007(main) This is output_printf print
15/09/2009, 15:38:24; test.c:0014(main) float = 6.79; int = 0456; string = output_printf-print-string
15/09/2009, 15:38:24; test.c:0017(main) test end
B. 定义 OPEN_DEBUG:
gcc -o test test.c dstdio.c -DOPEN_DEBUG
此时函数 debug_printf 和 output_printf 调用均输出。如下:
15/09/2009, 15:39:57; test.c:0005(main) test begin
15/09/2009, 15:39:57; test.c:0006(main) This is debug_printf print
15/09/2009, 15:39:57; test.c:0007(main) This is output_printf print
15/09/2009, 15:39:57; test.c:0012(main) i = 12345; s = 67890
15/09/2009, 15:39:57; test.c:0014(main) float = 6.79; int = 0456; string = output_printf-print-string
15/09/2009, 15:39:57; test.c:0017(main) test end
在项目中,我们可以用 debug_printf 输出只需要在调试查看的信息;用 output_printf 输出任何情况下均需要查看的信息。然后在编译时候,采用定义(或者不定义)OPEN_DEBUG宏的方法打开(或者关闭)调试信息。
- C语言应用系列: 01. 宏在项目中的应用
- c语言应用系列: 01. 宏在项目中的应用
- 数据结构在C语言中的应用
- static在C语言中的应用
- CONST在C语言中的应用
- extern 在C语言中的应用
- enum自学--在C语言中的应用
- Static在C语言中的应用
- C语言-数据类型在LR中的应用
- static关键字在c语言中的应用
- Protobuf-c在项目中的应用
- C语言可变参数在宏定义中的应用
- C语言中单井号(#)和双井号(##)在宏语句中的应用
- C语言可变参数在宏定义中的应用
- AngularJS在实际项目中的应用系列目录
- C语言中的位运算在嵌入式中的应用
- c语言宏定义中的#、##应用实例
- C语言在单片机开发过程中的应用
- 23岁的大专生怎样规划自己的开发之旅
- 自我介绍简单
- WINDOWS的内存管理【虚拟内存管理】(一)
- 8
- DataSet运用DES加解密到Xml
- C语言应用系列: 01. 宏在项目中的应用
- ^%$^
- 硬盘安装Windows 7的方法
- 收支管理——c语言 上海应用技术学院
- 如何抓取股票数据
- iReport .JRXML报表模板的元素
- 处理大数据量之分区表
- 安全防护之Windows八大保密技巧
- linux下c语言select函数用法