嵌入式C语言中宏的使用技巧
在整个嵌入式C语言系列中,我一直都是用嵌入式去限定C语言,而不是针对整个C语言,因为我们这里主要讨论的是嵌入式中的C语言。《C语言程序设计》这门课,大家基本上都在大学课程里学过或者有所了解,甚至可以说在这个C语言领域里,大家都是谭浩强老师的徒子徒孙。与大学理论课不同,我们这里讨论的是实际应用,而且是C语言在嵌入式领域的开发与应用。貌似现在只靠C语言就能混饭吃的,也只有嵌入式行业了。
下面进入正题,这里我们要讨论一下嵌入式C语言中关于宏的使用。
宏的用法
在C语言中,允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”(注意不是游戏里player killer),也叫宏定义。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。
宏定义和函数的区别
宏定义是由源程序中的宏定义命令完成的,而且是由预处理程序自动完成的。大家知道,预处理过程,是在程序编译链接之前就进行的,这就造就了宏定义的特有属性。它不同于函数定义,函数定义好了以后,是可以被反复调用的,但是被调用的函数都是程序空间中的同一段代码。宏定义就不同,宏定义其实是在程序编译之前,会被完全替换和反复展开的一段代码。
相比较而言,宏定义这种方式消耗了比较多的程序空间。函数虽然节省了空间,而且被调用的次数越多节省的空间也越多,但是你别忘了,函数调用是要消耗时间和其他资源的。这里面包括,调用之前的参数进栈保护 、形参与实参的数据传递、函数的跳入返回和函数调用返回的参数出栈还原。
总的来说,函数就是以时间换空间,减小程序代码空间,实现程序的“”高内聚“;宏定义就是以空间换时间,虽说增加了程序的代码空间,但是可以优化代码的响应速度。比如,在中断响应函数里,我们就可以尽量不采用函数调用的方法,更要杜绝函数的多层嵌套调用。因为这样容易消化过多的堆栈空间不说,还容易造成堆栈溢出,并且大大降低了程序的及时响应速度。
我们在实际项目开发的过程中,经常会用到一些位操作。因此我们可以定义一些常用的位操作宏函数,方便功能实现。
常用宏定义
#define SetiBit(reg,i) (reg |= ((t_uint32)1<<i))#define ClriBit(reg,i) (reg &= ~((t_uint32)1<<i))#define ChkiBit(reg,i) (((reg)&((t_uint32)1<<(i)))!= 0)
说明一下:SetiBit(reg,i)用于位置位;ClriBit(reg,i)用于位清零;ChkiBit(reg,i)用于位检测。这样我们在做一些针对变量的位操作时,会非常方便。
这种宏定义也适用于普通变量的封装调用,这一点类似于C++中,类对其访问私有数据成员的接口函数。比如,
#define GetRxMsg(msg,i) (C_RxMsg[msg].Byte[i])#define SetRxMsg(msg,i,val)(C_RxMsg[msg].Byte[i] = val)
说明一下:GetRxMsg(msg,i)用于数据读取;SetRxMsg(msg,i,val)用于数据写入。这样,相当于间接实现了某些数据变量的私有化属性,以便封装使用。
类似的宏定义还有很多,大家可以自己总结,最后形成自己的通用宏定义库文件。