C语言宏定义

宏定义的运用

一、常见用法
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",&macro_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",&macro_input1);
        scanf("%d",&macro_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()实现了将指定的函数指针放置到特定的段的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值