C/C++中宏定义的经典运用

发布者:幸福如意最新更新时间:2015-05-06 来源: 51hei关键字:C++  宏定义  经典运用 手机看文章 扫描二维码
随时随地手机看文章
在C语言中宏定义是比较有用的技巧,在Linux源码中经常使用一些宏定义,比如宏container_of()等都是经典的宏定义表示方式。在C++不再主张使用宏定义,但是宏定义实际上却是是一个非常有用的手段。实质上宏定义能够搞定的实现采用其它的实现也是可以的,宏定义的作用是简单的替代作用,掌握这个是理解的关键,以前在没有代码阅读量的时候总是以为宏定义就是简单的定义一些常量什么的,实质上不然,宏定义完全可以写成函数的形式,但是宏定义和函数有一定的差别,函数的调用一般采用栈的方式实现,这时候存在局部变量,形参、实参等问题,如果不理解C语言的本质,很多时候非常容易搞错,但是宏定义则不然,宏定义没有调用的过程,宏的实现仅仅是一个替换过程,不用压栈出栈,宏实现的类似函数修改的就是当前区域中的变量,不是局部变量。这也是一些较深层次的问题,在刚学习C语言的时候很多人只要看见类似于函数的形式都认为是函数,实质上不一定,很有可能就是采用宏定义实现的类函数,这时候就可能导致所谓的形参实参问题发生较大的变化。关于宏的问题在面试笔试的过程中、写代码的过程中都是非常有用的部分,我就不再做介绍。

   今天看了一遍博客《Reduce C-language coding errors with X macros》,感觉文章写得非常的好,也对自己写代码有了一定的帮助,所以就将该文章用我自己的语言,写出来和大家分享分享吧。

    在嵌入式实时操作系统中,经常将系统分成很多层次,很多个模块,每一个模块都有自己的初始化过程,这时候我们一般采用的形式如下所示:

    typedef void(*p_func_t)(void);

    enum
    {
        STATE_0,
        STATE_1,
        STATE_2,
        ...
        STATE_M,
        NUM_STATES
    };

    p_func_t inital_table[NUM_STATES]
    {
       func_0,
       func_1,
       func_2,
       ...
       func_M,
    };

   这种实现方法是比较常见的实现方式,但是这种方法的缺点是所有的初始化过程是按照一定的顺序的,而且不能随机的初始化,因此如果在编码的过程中将状态号与初始化函数对应错误,将出现比较难以发现的错误,这种错误经常出现,当然有些编译器以及支持随机的初始化过程,但是并不具有通用性,而且这种实现方式代码比较多,能不能采用宏定义的方式简化代码量呢?当然是可以的,采用类似于函数的宏定义就可以实现,具体的实现如下:

    typedef void(*p_func_t)(void);

    #define STATE_TABLE
        ENTRY(STATE_0,func_0)
        ENTRY(STATE_1,func_1)
        ENTRY(STATE_2,func_2)
        ENTRY(STATE_3,func_3)
        ENTRY(STATE_4,func_4)

    enum{
    #define ENTRY(a,b)    a,
        STATE_TABLE
    #undef ENTRY
      NUM_STATES
    };

    p_func_t inital_table[NUM_STATES] =
    {
    #define ENTRY(a,b)    b,
        STATE_TABLE
    #undef ENTRY
     };

   上面这种实现方式的优点是运用了宏定义简少代码量。我做一个简要的分析,首先采用宏定义定义了一组ENYRT,其中包含两个参数,分别是状态号STATE_N,和状态对应的初始化函数,这种实现方式能够避免上面所谓的状态号与函数对应错误的问题,因为在宏定义的过程中一般都会认真的确定各种接口,对应好了只需要定义相关的函数就可以啦。在enum中采用了#define和#undef来限定这一组宏定义的作用范围,在个作用域中,ENTRY(a,b)是表示“a,”,需要注意不能忽略a后的','因为这就是在enum中定义变量后要添加的符号,我想大家应该知道enum{a,b,c,d}每一个成员后面都包含","的特性的。也就是说在这作用域中,ENTRY(a,b)被替换为"a,",那么这时候STATE_TABLE就被替换为STATE_0,STATE_1等,然后和NUM_STATES就组成了第一个例程中的enum结构。而在p_func_t jumptable[NUM_STATES]仍然采用了了STATE_TABLE,由于采用了#define和#undef限定了宏的作用范围,这时的ENTRY(a,b)将被替代为“b,”,也就是func_0,func_1等,这样也就完成了函数指针数组的初始化过程,这样的初始化能够减少状态号与初始化函数对应出错的情况。

   这样的实现也可以认为是宏定义的巧妙运用,但是这种方法还是存在一些问题,因为采用#define 和#undef这种方法很可能导致错误的产生,因为很有可能不能很好的把握这个限定作用域的使用方法,这时候可以采用一种新的类似函数的实现方法,可以让STATE_TABLE带一个参数,也就是采用类似命令的形式定义相关的内容:

    typedef void(*p_func_t)(void);

    /*以下产生几个常用的命令*/
    /*enum产生*/
    #define EXPAND_AS_ENUM(a,b) a,
    /*初始化表产生*/
    #define EXPAND_AS_INITTABLE(a,b) b,
    /*声明各个函数*/
    #define EXPAND_AS_FUNCDEC(a,b) void b(void);

    /*将STATE_TABLE的参数就是具体的命令*/
    #define STATE_TABLE(ENTRY)
        ENTRY(STATE_0,func_0)
        ENTRY(STATE_1,func_1)
        ENTRY(STATE_2,func_2)
        ENTRY(STATE_3,func_3)
        ENTRY(STATE_4,func_4)

    /*定义enum*/
    enum{
      STATE_TABLE(EXPAND_AS_ENUM)
      NUM_STATES
    };

    /*声明各个函数*/
    STATE_TABLE(EXPAND_AS_FUNCDEC)

    /*初始化表*/
    p_func_t inital_table[NUM_STATES] =
    {
        STATE_TABLE(EXPAND_AS_INITTABLE)
     };

   以上实现方法能够较好的避免#define和#undef的限定作用域问题,这实际上采用ENTRY作为参数传递给STATE_TABLE,然后ENTRY可用来实现不同的指令,这些指令的定义也是一系列的宏定义,这种实现架构能够比较好的避免缺少声明等问题。同时也较少了错误的产生可能。[page]

这种实现模型只是简化的版本,STATE_TABLE(ENRTY)中的ENTRY可以定义多个参数如下所示:

    /*COMMANDS*/
    #define COMD1(a0,a1,a2,...,am) /*具体实现*/
    #define COMD2(a0,a1,a2,...,am) /*具体实现*/
    ...

    #define TABLE(ENTRY)
       ENTRY(a0,a1,a2,...,am)
       ENTRY(a0,a1,a2,...,am)
       ...
       ENTRY(a0,a1,a2,...,am)

   比如将上面的初始化实现修改为下面的形式,也就是多个参数的形式,实现如下:

点击(此处)折叠或打开

    typedef void(*p_func_t)(void);

    #define EXPAND_AS_ENUM(a,b,c) a,

    #define EXPAND_AS_JUMPTABLE(a,b,c) b,

    #define EXPAND_AS_FUNCDEC(a,b,c) void c(void);

    #define STATE_TABLE(ENTRY)
        ENTRY(STATE_0,func_0,func_0)
        ENTRY(STATE_1,func_1,func_1)
        ENTRY(STATE_2,func_2,func_2)
        ENTRY(STATE_3,func_3,func_3)
        ENTRY(STATE_4,func_4,func_4)


    enum{
      STATE_TABLE(EXPAND_AS_ENUM)
      NUM_STATES
    };

    STATE_TABLE(EXPAND_AS_FUNCDEC)

    p_func_t init_table[NUM_STATES] =
    {
       STATE_TABLE(EXPAND_AS_INITTABLE)
    };

   上面的实现并不是非常的恰当,因为第二个、第三个参数实质上是一致的,没有必要定义为三个参数。本文只是说明三个参数的实现情况。其他多个参数的实现情况类似。为了说明这种模型的可行性,我写了简单的测试代码,由于各个模块的初始化代码需要程序员手动的实现,因此可以定义在其他的位置,同时在宏定义中也已经实现了各个函数的声明问题,因为不会出现未定义的问题。具体的实现如下所示:

    #include

    typedef void(*p_func_t)(void);

    #define EXPAND_AS_ENUM(a,b) a,

    #define EXPAND_AS_INITTABLE(a,b) b,

    #define EXPAND_AS_FUNCDEC(a,b) void b(void);

    #define STATE_TABLE(ENTRY)
        ENTRY(STATE_0,func_0)
        ENTRY(STATE_1,func_1)
        ENTRY(STATE_2,func_2)
        ENTRY(STATE_3,func_3)
        ENTRY(STATE_4,func_4)


    enum{
      STATE_TABLE(EXPAND_AS_ENUM)
      NUM_STATES
    };

    STATE_TABLE(EXPAND_AS_FUNCDEC)

    p_func_t init_table[NUM_STATES] =
    {
        STATE_TABLE(EXPAND_AS_JUMPTABLE)
     };

 

    /*测试代码*/
    int main()
    {
        int i = 0;

        for(i = 0; i < NUM_STATES; ++ i)
            (jumptable[i])();

        return 0;
    }

    /*各个模块的初始化函数实现*/
    void func_0(void)
    {
        printf("In func_0 ");
    }

    void func_1(void)
    {
        printf("In func_1 ");
    }

    void func_2(void)
    {
        printf("In func_2 ");
    }

    void func_3(void)
    {
        printf("In func_3 ");
    }

    void func_4(void)
    {
        printf("In func_4 ");
    }

   关于多变量的情况,在Linux内核源码中的物理内存与虚拟内存之间可以采用这种方式实现,在很多情况下都知道寄存器的物理内存,一般一组相关的寄存器的映射方式都是相同的,采用这种宏定义的实现方式就能较好完成定义问题,当然只是可行的方法而已。

    #define ADDRESS_OFFSET (0x8000)
    #define PHYS_ADDRESS(a,b,c) a=c;
    #define VIRU_ADDRESS(a,b,c) a = b + c;

    #define REGISTER_MAP(ENTRY)
       ENTRY(reg0,ADDRESS_OFFSET,0x10)
       ENTRY(reg1,ADDRESS_OFFSET,0x14)
       ENTRY(reg2,ADDRESS_OFFSET,0x18)
       ...
       ENTRY(regm,ADDRESS_OFFSET,0x2c)

    /*物理地址*/
    REGISTER_MAP(PHYS_ADDRESS)
    /*虚拟地址*/
    REGISTER_MAP(VIRU_ADDRESS)

   宏定义的这种实现方式在一些嵌入式系统中是非常有效的,掌握这种实现方法能够较好的管理各个模块、各种状态下对应的处理函数。这是一种经典的用法。这实际上给出可一种解决问题的模型架构,掌握好这种方式能够较好的实现模块的管理问题。这种实现方法不仅代码量少,而且能够避免很多错误的产生,能够快速的进行修改,但是难点在于代码对于初学者有一定的难度,而且宏定义实现的函数还能够采用其他的方法实现,只是宏定义能够较好的简化代码,使得代码优美且易维护。

关键字:C++  宏定义  经典运用 引用地址:C/C++中宏定义的经典运用

上一篇:异或运算在算法中的经典运用
下一篇:C语言函数调用分析

推荐阅读最新更新时间:2024-03-16 14:00

在仿真环境下实现TMS320C6000系列DSP的程序自引导
摘要:介绍了TMS320C6000系列DSP在仿真环境下对闪速存储器(FLASH)的C语言编程方法,同时根据这种DSP的程序自引导机制(boot loader),介绍了从FLASH进行引导的新途径,从而为TMS320C6000系列DSP的开发提供了一种新的思路。 关键词:TMS320C6000;FLASH;boot loader 开发DSP系统应用板,最终要脱离仿真器而独立运行,这时就需要一个能在断电后保存程序及初始化数据的存储器。系统上电时,由引导程序将DSP的应用程序从该存储器引导到DSP应用板上的高速存储器(如内部SRAM,SDRAM等)中。由于FLASH具有电信号删除功能且删除速度快,集成度高,因而已成为此种存储器的
[应用]
C&K Components开发一系列PCB安装型短行程按键开关
C&K Components已经开发了一系列的PCB安装型短行程按键开关,带有一个可选的弯脚端子,加速了双面PC板的安全安装。 PHB系列短行程按键开关设计用于通孔PCB焊接,并且适用于双孔(操纵力170g / 1.67N)或四孔(操纵力230g / 2.26N)配置。总的行程距离为2.5mm 0.5mm;锁住行程(适用于开关动作)为1.5mm 0.5mm。利用一种经济且可靠的封装接触设计,PHB系列开关理想用于计算机与外设,仪器仪表和测量应用的各种无功率,打开/关闭的开关电路。 额定电流为1A@15V DC,PHB系列开关具有20兆欧(初始值)的接触电阻和100兆欧绝缘电阻,在额定负载下的工作寿命为10,
[半导体设计/制造]
<font color='red'>C</font>&K Components开发一系列PCB安装型短行程按键开关
USB-IF提出C型USB接头 苹果Lightning接头压力渐增
    苹果(Apple)在2012年9月发表iPhone 5智慧型手机,首度改用苹果新创、仅8-pin线路的Lightning连接器。Lightning除较原有30-pin连接器娇小外,无论正接、反接均可正常运作,是相当体贴的防呆设计,因此这个设计想法引起业界相当多关注。 2014年4月,负责订立与管理USB标准的USB-IF(USB Implementers Forum)提出新的接头标准,即C型USB接头,新接头同样为正接、反接均可正常运作,4月为0.7版,5月则提出0.9版,目前尚未进入正式的1.0版。 C型接头除标榜防呆机制外,也诉求支援最新的USB相关标准,例如支援最新的USB 3.1标准,可达10Gbps传输率,或支援新
[手机便携]
【s3c2440】第二课:arm汇编指令
s3c2440 arm汇编指令以及使用示例 首先需要了解s3c2440CPU内部的寄存器有哪些: CPSR/SPSR寄存器格式: arm指令 (1)运算指令 (2)跳转指令 (3)协处理器相关指令 (4)数据转移指令 tips: db(Decrement Before):先减后存 ib(Increment Before):先增后存 da(Decrement After):先存后减 ia(Increment After):先存后增 (5)异常处理指令
[单片机]
【s3<font color='red'>c</font>2440】第二课:arm汇编指令
C51---1 新建C51工程 + 2.1 并点亮LED灯 + 3.1 按键控制LED亮灭
1 新建工程 选择好目录路径后,选择 添加文件 2.1 并点亮LED灯 LED原理图 main函数代码 led1为P2_0 P2控制8位设置为1111 1110时第0位为0,LED二极管导通点亮。 #include REGX52.H void main() { P2=0xFE;//1111 1110 while(1); } 烧录后 点亮4颗led #include REGX52.H void main() { P2=0x55;//0101 0101 while(1); } 3.1 按键控制LED亮灭 原理图 main函数 按键1按下时,LED1亮
[单片机]
<font color='red'>C</font>51---1 新建<font color='red'>C</font>51工程 + 2.1 并点亮LED灯 + 3.1 按键控制LED亮灭
89C2051单片机无线遥控电铃的工作原理
如电路图所示。AC220V市电经降压、整流、滤波后,得到12V直流电压,再经稳压得到Sv直流电压,分别为继电器J1、89C2051单片机和315接收模块供电。 未按遥控器的A键时,接收模块⑩脚没有指令信号输出,单片机Pl.0口的绿色指示灯Dl每隔0.5秒闪一下,Dl既作电源指示又作遥控指令信号。此时,如果按下遥控器的A键,接收模块收到信号后从⑩脚输出高电平信号到单片机的P3.0口,单片机从P1.2口输出低电平,P1.1口输出高电平,令绿灯灭红灯亮,同时经三极管Q1驱动继电器J1吸合,电铃响10秒后恢复初始状态。完整的程序清单如下(已通过调试)。
[单片机]
89<font color='red'>C</font>2051单片机无线遥控电铃的工作原理
基于S3C4510B的存储系统原理与设计
1.     引言        对于嵌入式系统的开发人员来说,深刻地理解其存储系统的寻址原理和有效的管理存储系统对正确高效地设计嵌入式系统的硬件和底层软件编程具有重要的意义。目前嵌入式系统中最常用的存储器包括有EEPROM、FLASH、Normal DRAM和Sync.DRAM等。本文所采用的存储器包括有SDRAM和FLASH。S3C4510B(以下简称4510)微处理器是构建在ARM核ARM7TDMI之上的,ARM7TDMI的地址总线为32位的,4510的内部系统总线却是26bit:SA ,它的外部地址总线却是22bit:ADDR ,它们之间是如何译码的,而仅用14根外部地址总线为什么能够访问多达16MB的内存地址空间,本文
[单片机]
基于S3<font color='red'>C</font>4510B的存储系统原理与设计
带RTC的I2C总线铁电存储器FM31256
FM31256是由Ramtron公司推出的新一代多功能系统监控和非易失性铁电存储芯片。与其他非易失性存储器比较,它具有如下优点: 读/写速度快,没有写等待时间;功耗低,静态电流小于1 mA,写入电流小于150 mA;擦写使用寿命长,芯片的擦写次数为100亿次,比一般的EEPROM存储器高10万倍,即使每秒读/写30次,也能用10年;读/写的无限性,芯片擦写次数超过100亿次后,还能和SRAM一样读/写。 铁电存储器(FRAM)的核心技术是铁电晶体材料。这一特殊材料使铁电存储器同时拥有随机存取存储器(RAM)和非易失性存储的特性。本文介绍了FM31256的主要功能,并具体给出了基于嵌入式C语言编写的存储器读/写程序。 1 FM31
[应用]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved