宏定义的运用
一、常见用法
1 防止头文件重复包含
#ifndef _MACRO_HEAD_H_
#define _MACRO_HEAD_H_
……
#endif //#ifndef _MACRO_HEAD_H_
2 普通的常量define
#define MACRO_MAX_SIZE 100
…
char macro_test[ MACRO_MAX_SIZE ] = { 0 };
此类宏定义的作用主要是方便修改数组的大小,减少修改数组长度时可能发生的潜在的错误。
#define NULL 0
#define TRUE 1
#define FALSE 0
3 选择编译
#ifdef MACRO_OPTION
……// case 1
#else
……// case 2
#endif
如果定义了MACRO_OPTION,则编译case 1处的代码段,如果未定义则编译case 2处的代码段。
4 类型定义
typedef unsignedchar BYTE
typedef unsigned short WORD
typedef unsigned int DWORD
typedef longlong DDWORD
……
当然,上述定义根据具体编译器而定。
5.定义常用操作
1.获取两个值中的较大值或者较小值
#define MACRO_MAX(a, b) ( ( a ) > ( b ) ? ( a ) : ( b ) )
#define MACRO_MIN(a, b) ( ( a ) > ( b ) ? ( b ) : ( a ) )
2 获取两个值的和、差
#define MACRO_ADD(a, b) ( ( a ) + ( b ) )
#define MACRO_SUB(a, b) ( ( a ) - ( b ) )
3 获取绝对值
#define MACRO_ABS(a) ( ( a ) > 0 ? ( a ) : ( -( a ) ) )
4 获取变量的地址
#define MACRO_GET_ADDR(a) (&a)
5 设置寄存器的值
#define MACRO_SET_BYTE( addr, val_byte ) ( *( (volatile unsigned char* )( addr ) ) = val_byte)
#define MACRO_SET_WORD(addr,val_word ) ( *( (volatile unsigned short* )( addr ) ) = val_word )
#define MACRO_SET_WORD(addr,val_dword ) ( *( (volatile unsigned int* )( addr ) ) = val_dword )
二、高级用法
1. #与##
#预处理运算符主要用于将其修饰的变量转换为字符串。
##预处理运算符主要用于连接前后两个参数
Sample:
#include <stdio.h>
#define test_cat(a,b) a ## b
#define test(a,b)test_cat(a,b)
#define _str(a) #a
#define str(a)_str(a)
void main()
{
printf("%s\n",test(test("a","b"),"c"));
printf("%s\n",test(test_cat("a","b"),"c"));
// printf("%s\n",test_cat(test("a","b"),"c"));
// printf("%s\n",test_cat(test_cat("a","b"),"c"));
printf("%s\n",_str(test(test(1,2),3)));
printf("%s\n",str(test(test(1,2),3)));
printf("%s\n",_str(__LINE__));
printf("%s\n",str(__LINE__));
}
编译:cl macro_test.c
运行:macro_test
输出结果为:
abc
abc
test(test(1,2),3)
123
__LINE__
16
将11、12行注释打开,cmd中执行
预处理:cl/P macro_test.c
生成macro_test.i文件,上述代码经过预处理后如下所示:
void main()
{
printf("%s\n","a""b""c");
printf("%s\n","a""b""c");
printf("%s\n",test_cat("a","b")"c");
printf("%s\n",test_cat("a","b")"c");
printf("%s\n","test(test(1,2),3)");
printf("%s\n","123");
printf("%s\n","__LINE__");
printf("%s\n","16");
}
下面结合实验结果进行分析:
第11、12行是编译不过的,原因如macro_test.i文件中所示,test_cat(test("a","b"),"c")预处理后的结果是test_cat("a","b")"c"),test_cat(test_cat("a","b"),"c")预处理后的结果也是test_cat("a","b")"c"),这个编译显然会报错,为什么宏没有完全展开呢?原因在于test_cat宏展开的时候里面有##,对于#和##宏,在展开一次后再次遇到时不会被展开,因此外层可以顺利的展开,而内层的参数只能展开到test_cat这一层,不会再往下展开。
第9、10行,由于test本身的扩展中并没有##或#,因此内外层都能被顺利的展开。
第13、15行,_str(test(test(1,2),3))和_str(__LINE__),由于展开后变成#test(test(1,2),3)和#__LINE__,test和__LINE__不会再展开
第14、16行,由于是两次宏,展开的过程如下:
str(test(test(1,2),3)) _str(123) #123 “123”
str(__LINE__) _str(16) #16 “16”
由上面的分析可知,我们在处理有#和##的宏的时候要及其小心,一不留神就会出现意想不到的bug,得不到我们想要的结果,避免这种问题的方法是定义二次宏,使用的时候通过二次宏将参数先展开,再传递给底层宏,这样,就可以得到正确的结果了。
2.实现较复杂函数功能
#include <stdio.h>
#define jdg_func(macro_input, pret )\
do{\
int len = 0;\
int icnt = 0;\
char p[10] = {0};\
while(macro_input!= 0)\
{\
p[ icnt ] = macro_input % 10;\
macro_input = macro_input / 10;\
icnt++;\
}\
len= icnt;\
for(icnt= len - 1; icnt >= 0; icnt--)\
{\
printf("%d",p[icnt]);\
}\
printf("\n");\
for(icnt= 0; icnt < len/2 +1; icnt++ )\
{\
if(*(p + icnt ) != *( p + len - 1 - icnt ))\
{\
*pret= -1;\
}\
}\
}while(0)
void main()
{
int macro_input = 0;
int ret = 0;
while(1)
{
printf("#");
scanf("%d",¯o_input);
jdg_func(macro_input,&ret);
if(ret == 0 )
{
printf("YES\n");
}
else
{
printf("NO\n");
ret= 0;
}
}
}
上面的宏jdg_func的作用是判断输入的一个整数是否是回文数,上面主要展示一下使用宏定义实现一个类似函数的功能,与函数的区别主要在于:1.使用宏定义的执行速度比函数快2.如果代码中多次调用的话,使用宏定义占用的空间比函数大。3.使用宏定义无法像函数一样使用return来返回值。
3.变参数宏
#include <stdio.h>
#define debug(fmt, args...) printf(fmt, args)
//#define debug(...) printf(__VA_ARGS__)
void main()
{
int macro_input1 = 0;
int macro_input2 = 0;
int ret = 0;
while(1)
{
printf("#");
scanf("%d",¯o_input1);
scanf("%d",¯o_input2);
debug("%d,%d\n",macro_input1,macro_input2);
}
}
第三行指定了变参名args,第四行未指定变参名,可用默认宏__VA_ARGS__来表示。两行的作用是一样的。
注:上述这段代码用cl编译报错,原因在于cl编译器不完全支持c99协议,改用gcc编译可顺利通过。
4.将指定的代码、数据放置到特定的段中
这里用linux中例子来说明,熟悉linux的对下面两个宏肯定不会陌生
module_init()
module_exit()
两个宏的作用是注册初始化函数和注销函数,在设备驱动开发中常常用到。
它们在\include\linux\Init.h中定义:
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);
1.先来分析module_init(x):
module_init(x)相关宏定义:
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall"level ".init"))) = fn
#define __used __attribute__((__used__))
typedef int (*initcall_t)(void); //函数指针
结合上面几个宏定义,最终可展开为:
#define module_init(x) \
static initcall_t __initcall_##x##6 __attribute__((__used__)) \
__attribute__((__section__(".initcall""6" ".init"))) = x
对于RTC驱动,假如rtc_init()为RTC初始化函数,使用module_init(rtc_init)后,在原地展开的代码如下:
static initcall_t __initcall_rtc_init6 __attribute__((__used__)) \
__attribute__((__section__(".initcall""6" ".init"))) = rtc_init;
这段代码的作用是在.initcall6.init 段中定义一个函数指针__initcall_rtc_init6,指针指向函数rtc_init,.initcall6.init段中的函数在系统初始化的时候会被统一调用(取决于模块是被编译进内核还是动态加载),这样,相关的模块就被初始化了。
2.对于module_exit(x),如下所示:
module_exit(x)相关宏定义:
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = f n
#define __exit_call __used__section(.exitcall.exit)
#define __used __attribute__((__used__))
typedef void (*exitcall_t)(void);
从而,宏最终展开如下:
#define module_exit(x) \
static exitcall_t __exitcall_##x __attribute__((__used__))
__attribute__((__section(.exitcall.exit)__)) = fn
对于RTC驱动,假如rtc_exit()为RTC注销函数,使用module_exit(rtc_exit)后,在原地展开的代码如下:
static exitcall_t __exitcall_rtc_exit __attribute__((__used__))__
__attribute__((__section(.exitcall.exit) __)) = rtc_exit;
这段代码的作用是在.exitcall.exit段中定义一个函数指针__exitcall_rtc_exit,指针指向函数rtc_exit,__exitcall_rtc_exit段中的函数在系统注销的时候会被统一调用(取决于模块是被编译进内核还是动态加载),这样,相关的模块就被注销了。
这样通过两个宏module_init()、module_exit()实现了将指定的函数指针放置到特定的段的功能。