C语言中的宏MACRO的用途

++++++++++++++++++++++
1)硬码实义化
用宏来将一个硬码数值,进行实义化。、

#define DEBUG_CONSOLE_DEVICE_TYPE_NONE    0U     /*!< No debug console.             */
#define MCU_MEM_MAP_VERSION               0x0300U

#define ADC_HC_ADCH_MASK                     (0x1FU)

#define FSL_IOMUXC_DRIVER_VERSION (MAKE_VERSION(2, 0, 0))

在编写代码的过程中,尽量不要出现硬码,如果需要使用硬码,也要使用实义宏。
这样便于将函数模板化。
在需要修改时,只修改实义宏即可,无需修改函数。

另一种硬码实义化的方式,是enum。
enum分为枚举常量和枚举变量。
在定义enum时,花括号里列出的,就是枚举常量。
而在代码中用内存分配的,就是枚举变量。

枚举常量,就是实义化的硬码。
它和实义宏是有区别的,
实义宏是一个纯数值,不具类型。
枚举常量是具类型的。
对一个枚举变量赋值时,会检查类型是否匹配,所以需要强转。

+++++++++++++++++++++++++++++++++++++
2)参数块实义化
在调用函数时,需要传参,参数列表的一部分,我们称为参数块。
如果参数块,是硬码形式的,即这个参数块中,全是数值,
那么可以将参数块实义化,定义为宏,
这个宏,就是宏拟参数块。

#define IOMUXC_GPIO1_IO02_GPIO1_IO02                         0x020E0064U, 0x5U, 0x00000000U, 0x0U, 0x020E02F0U

注意,参数块中间的多个硬码,要用逗号分隔,但是参数块的前面和后面,都不要有逗号。
这样,使得参数块,看起来就像一个参数一样。

在编写代码的过程中,尽量不要出现硬码,如果需要使用硬码,也要使用实义宏。

++++++++++++++++++++++++++++++++
3)宏拟变量
用宏来将MMIO或者MMREG进行实义化。
对MMREG的读写,就像对一个变量读写一样。

#define CCM_CCGR5 *((volatile unsigned long*)0x020C4080)

绝对地址,是一个指针,将其强转,并加以volatile修饰。
对指针取数,即为变量。
这就是宏拟变量。

+++++++++++++++++++++++++
4)宏拟句柄
用宏来将MMIORegion或者MMRegRegion进行实义化。

typedef struct
{
    volatile unsigned int CCR;
    volatile unsigned int CCDR;
	volatile unsigned int CSR;
	volatile unsigned int CCSR;
}CCM_Type;

#define CCM_BASE                    (0X020C4000)   

#define CCM                 ((CCM_Type *) CCM_BASE)

首先通过定义结构体对象,将MMRegRegion定义到一个Block中。每个成员都有volatile修饰符,表示这是一个MMREG。
命名通常用后缀_Type标识。
然后用宏来定义一个BaseAddress。
基址宏,通常用后缀_BASE标识。
然后将基址宏强转,成为一个结构体对象的指针类型,并实义化为一个宏。
我们称一个结构体对象的指针,为句柄。
这个宏就是宏拟句柄。

+++++++++++++++++++++++++++++++++++++++
5)宏拟函数

#define MAKE_VERSION(major, minor, bugfix) (((major) << 16) | ((minor) << 8) | (bugfix))

#define NXP_VAL2FLD(field, value)    (((value) << (field ## _SHIFT)) & (field ## _MASK))

注意,宏拟函数的宏体,必须用一个总括号包含起来。最后不能有分号。
宏拟函数的作用,是将一个操作语句块,或者操作表达式,实义化,模板化。
函数在编译时,会被编译成可调用代码段。这就会涉及到调用开销,
但是宏拟函数,编译时,会就地展开,成为内联代码段。

另一种内联代码段的方法,是static inline 函数。

static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
                                    uint32_t muxMode,
                                    uint32_t inputRegister,
                                    uint32_t inputDaisy,
                                    uint32_t configRegister,
                                    uint32_t inputOnfield)
{
    *((volatile uint32_t *)muxRegister) =
        IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) | IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);
    if (inputRegister)
    {
        *((volatile uint32_t *)inputRegister) = IOMUXC_SELECT_INPUT_DAISY(inputDaisy);
    }
}

static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
                                       uint32_t muxMode,
                                       uint32_t inputRegister,
                                       uint32_t inputDaisy,
                                       uint32_t configRegister,
                                       uint32_t configValue)
{
    if (configRegister)
    {
        *((volatile uint32_t *)configRegister) = configValue;
    }
}

内联函数的写法,能够描述更复杂的代码块。
宏拟函数,通常用来描述较简洁的代码,例如表达式。

++++++++++++++++++++++++++++++++++
6)头文件保护,宏标志,宏开关

#ifndef __MAIN_H
#define __MAIN_H
	...
#endif

检查是否存在宏标志,
有则跳过,没有则打上宏标志。

++++++++++++++++++++++++++++++++
7)宏拟声明

宏定义一个变量声明或者函数声明的模板,在使用时,根据传给宏的宏参数,将宏模板实例化。
这样,就用一个简洁的形式,完成了复杂的长串的声明。

宏拟声明,形式上类似于宏拟函数,
区别在于,宏拟函数,求的是表达式的返回值,
而宏拟声明,并没有返回值。

例如,
U_BOOT_CMD,这是个典型的宏拟声明。

U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)

如下是一个宏拟声明的实例化,

U_BOOT_CMD(
	bootm,	CONFIG_SYS_MAXARGS,	1,	do_bootm,
	"boot application image from memory", bootm_help_text
);

来看看宏拟声明是怎么实现的,

typedef struct cmd_tbl_s	cmd_tbl_t;

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL) \

使用了中间宏,
U_BOOT_CMD_COMPLETE
这个中间宏,扩展了一个宏参数,

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
	ll_entry_declare(cmd_tbl_t, _name, cmd) = U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	_usage, _help, _comp);

使用了中间宏,
ll_entry_declare
这个中间宏,定义如下,

#define ll_entry_declare(_type, _name, _list)				\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
			__attribute__((unused,section(".u_boot_list_2_"#_list"_2_"#_name)))

使用了中间宏,
U_BOOT_CMD_MKENT_COMPLETE
这个中间宏,定义如下,

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,_usage, _help, _comp)			\
		{ 
			#_name, 
			_maxargs,	 
			_rep ? cmd_always_repeatable : cmd_never_repeatable,	 
			_cmd, 
			_usage, 
			_CMD_HELP(_help) 
			_CMD_COMPLETE(_comp) 
		}

宏体用花括号包含成一个整体。

使用了多层中间宏,经过层层宏展开后,得到如下结果

U_BOOT_CMD(
	bootm,	CONFIG_SYS_MAXARGS,	1,	do_bootm,
	"boot application image from memory", bootm_help_text
);

====展开得到====> 

U_BOOT_CMD_COMPLETE(bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,"boot application image from memory", bootm_help_text, NULL)

====展开得到====> 
ll_entry_declare(cmd_tbl_t, bootm, cmd) = 
		{ 
			#bootm, 
			64,	 				// #define CONFIG_SYS_MAXARGS      64  /* max command args */
			cmd_always_repeatable,	 
			do_bootm, 
			"boot application image from memory", 
			bootm_help_text,
			NULL
		}
		
====展开得到====> 

cmd_tbl_t _u_boot_list_2_cmd_2_bootm __aligned(4) __attribute__((unused,	section(".u_boot_list_2_cmd_2_bootm")))= 
{ 
			#bootm, 
			64,	 				// #define CONFIG_SYS_MAXARGS      64  /* max command args */
			cmd_always_repeatable,	 
			do_bootm, 
			"boot application image from memory", 
			bootm_help_text,
			NULL
}

U_BOOT_CMD宏拟声明的最终结果,是定义声明了一个结构体对象,并填充了结构体对象的各个成员。

注意,
宏拟声明,经常会用到#和##这两个预编译文本操作符。

#,是串化符,将宏参数转换成字符串。
具体过程是,当预编译进行文本处理时,首先将宏替换成宏体,然后将宏体中的宏参数,用实例宏参数进行文本替换,然后,再将串化符所关联的宏实参,加上双引号。
如果宏实参,仍是一个宏,那么并不会对宏先展开,再串化。而是直接将宏名串化。

##,是拼接符,将左右两个宏参数拼接成一个文本。
具体过程是,当预编译进行文本处理时,首先将宏替换成宏体,然后将宏体中的宏参数,用实例宏参数进行文本替换,然后,再将拼接符摘掉,。
如果宏实参,仍是一个宏,那么并不会对宏先展开,再拼接。而是直接将宏名拼接。

这里涉及到预编译文本处理时,对含宏的文本的处理过程。
含宏文本的处理,是层层迭代进行的。
先对外层宏进行处理,再扫描展开的宏体,如果含宏,则继续进行宏处理。

宏处理有如下几种常见的形式,
1 直接宏替换
如果识别出一个有效宏,且是无参宏,则将宏直接用宏体文本替换。

2 带参宏展开
如果识别出一个有效宏,且是带参宏,特征是宏名配有圆括号()操作符,则将带参宏,按照带参宏的处理方式处理。
具体执行两个步骤,
第一个步骤是宏替换,用宏体文本替换带参宏,
第二个步骤是宏体文本中的宏参数替换,用宏实参替换掉宏体文本中的宏形参。

3 宏串化
如果识别出一个宏实参,前面有#操作符,则按照宏串化的处理方式处理。

4 宏拼接
如果识别出一个宏实参,前面或者后面有##操作符,则按照宏拼接的处理方式处理。

每个有效宏,只会被处理一次,所以,经常 要用到多层中间宏。

例如:

#define STR(s)     #s 

	printf(STR(vck));           // 输出字符串"vck" 

首先识别出pirntf语句中,有个有效宏STR,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。
展开后,再次扫描,发现宏实参前面有个#操作符需要继续处理,再处理串化操作符。
处理完后,发现没有需要进行宏处理的位置了。
宏处理结束。

#define CONS(a,b)  (int)(a##e##b) 

    printf("%d\n", CONS(2,3));  // 2e3 输出:2000   2e3是科学计数法  2 * 10 ^3

首先识别出pirntf语句中,有个有效宏CONS,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。
展开后,再次扫描,发现宏实参的前后,有两个##操作符需要继续处理,再处理拼接操作符。
处理完后,发现没有需要进行宏处理的位置了。
宏处理结束。

#define MAX_NUM 7000
#define INT(x)  #x

printf("%s", INT(MAX_NUM));

//宏定义展开后:
//printf("%s", "MAX_NUM");

首先识别出pirntf语句中,有个有效宏INT,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。
展开后,再次扫描,发现有个宏实参MAX_NUM,是个宏,它有两种处理方式,一是直接宏替换,二是宏串化,显然,宏串化优先级更高,所以这里,这个宏,被执行了宏串化,后面也不会执行宏替换了。
处理完后,发现没有需要进行宏处理的位置了。
宏处理结束。

#define X  2
#define CON(x)  int(x##x)

printf("%d", CON(X));
//宏定义展开后:
//printf("%d", int(XX));

首先识别出pirntf语句中,有个有效宏CON,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。
展开后,再次扫描,发现有个宏实参X,是个宏,它有两种处理方式,一是直接宏替换,二是宏拼接,显然,宏拼接优先级更高,所以这里,这个有效宏,被执行了宏拼接,后面也不执行宏替换。
处理完后,发现没有需要进行宏处理的位置了。
宏处理结束。

#define MAX_NUM 7000

#define _STR(x) #x        //转换宏
#define STR(x)  _STR(x)

printf("%s", STR(MAX_NUM));

//宏定义展开
//printf("%s", "7000");

首先识别出pirntf语句中,有个有效宏STR,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。
展开后,再次扫描,发现一个衍生宏,是_STR,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。另一个是宏实参MAX_NUM,是个无参宏,且它是上一层宏传递过来的宏实参,按照预编译规则,宏实参不能再继续向下层传递,必须本层被处理掉。
在这一层,宏实参MAX_NUM既没有#可以结合,也没有##可以结合,
所以,这里,它被执行了宏替换。
然后,再处理带参宏_STR,此时,它的宏实参,已经是宏替换后的7000了。
展开后,再次扫描,发现宏实参7000前面有个串化符#,和宏实参7000结合,所以宏串化操作,被处理成了“7000”。
宏处理结束。

#define X  2

#define _CON(x) int(x##x)
#define CON(x)  _CON(x)    //转换宏

printf("%d", CON(X));
 
//宏定义展开
//printf("%d", int(22));

首先识别出pirntf语句中,有个有效宏CON,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。
展开后,再次扫描,发现一个衍生宏,是_CON,是个带参宏,和圆括号操作符结合,需要进行带参宏展开。另一个是宏实参X,是个无参宏,且它是上一层宏传递过来的宏实参,按照预编译规则,宏实参不能再继续向下层传递,必须本层被处理掉。
在这一层,宏实参X既没有#可以结合,也没有##可以结合,
所以,这里,它被执行了宏替换。
然后,再处理带参宏_CON,此时,它的宏实参,已经是宏替换后的2了。
展开后,再次扫描,发现宏实参2前面有个拼接符##,和宏实参2结合,所以宏拼接操作,被处理成了int(22)。
宏处理结束。

来看看多层中间宏的更复杂的例子,

#define  ___ANONYMOUS1(type, var, line)  type  var##line 
#define  __ANONYMOUS0(type, line)  ___ANONYMOUS1(type, _anonymous, line) 
#define  ANONYMOUS(type)  __ANONYMOUS0(type, __LINE__) 


ANONYMOUS(static int); 
//即: static int _anonymous70; 70表示该行行号;
//第一层:ANONYMOUS(static int); 
=》 第二层,__ ANONYMOUS0(static int, __LINE __ );
=》第三层, ___ANONYMOUS1(static int, _anonymous, 70);
=》第四层,最终展开, static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第三层才能被解开;

第一层的宏体,使用了第二层的宏名,并附加了额外的宏实参,
第二层的宏体,又使用了第三层的宏名,并附加了额外的宏实参。
第一层的宏实参,是static int,传递给第二层后,形成了第二层的带参宏的实例,
第二层的宏实参,有一个宏__LINE__,被传递到第三层,不能继续传递,只能被宏替换,
第三层衍生出带参宏,第四层完全展开。

#define  FILL(a)   {a, #a} 
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};

//相当于
//MSG _msg[] = 	{
					{OPEN, "OPEN"}, 
//             		{CLOSE, "CLOSE"}
//				}; 

发现一个带参宏,宏展开后,发现宏实参和#操作符结合,然后处理#操作。

再看几个例子。

#define  _GET_FILE_NAME(f)   #f 
#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f) 
static char  FILE_NAME[] = GET_FILE_NAME(__FILE__); 

=》
static char  FILE_NAME[] = "mytest.txt"; 




#define  _TYPE_BUF_SIZE(type)  sizeof #type 
#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type) 
char  buf[TYPE_BUF_SIZE(INT_MAX)]; 

例如:
char buf[_TYPE_BUF_SIZE(2147483647)];

=》 char buf[sizeof(“2147483647”)];
=》 char buf[11];

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值