C语言:预处理与宏定义详解

本文最后更新于:2022年9月4日 下午

预处理的步骤

C编译器做语法解析之前的预处理步骤

  • 对define语句进行替换
    • \可以把分行的代码连接成一行
    • 将注释替换为一个空格

宏可被\分割

#define STR “hello, “ \
“world”

#define MAIN \
int main() \
{ \
return 0; \
}

内核中出现了大量的复杂宏定义,因此深入宏是十分必要的

一个神鬼莫测的宏的示例

条件预处理指示

两个示例:

1
2
3
4
5
#ifndef <token>
/* code */
#else
/* code to include if the token is defined */
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#if DLEVEL > 5
#define SIGNAL 1
#if STACKUSE == 1
#define STACK 200
#else
#define STACK 100
#endif
#else
#define SIGNAL 0
#if STACKUSE == 1
#define STACK 100
#else
#define STACK 50
#endif
#endif
#if DLEVEL == 0
#define STACK 0
#elif DLEVEL == 1
#define STACK 100
#elif DLEVEL > 5
display( debugptr );
#else
#define STACK 200
#endif

宏定义

变量式宏定义(object-like macro)

宏定义名可以像变量一样在代码中使用,形式类似:
#define STR "hello, world"

函数式宏定义(function-like macro)

宏定义可以像函数调用一样在代码中使用,形式类似:
#define MAX(a, b) ((a)>(b)?(a):(b))

函数式宏定义的优势(相比较函数)

  • 省去了分配和释放栈帧、传参、传返回值等一系列工作
  • 简短并且被频繁调用的函数经常用函数式宏定义来代替实现。例如C标准库的很多函数都提供两种实现,一种是真正的函数实现,一种是宏定义实现
  • inline关键字告诉编译器,这个函数的调用要尽可能快,可以当普通的函数调用实现,也可以用宏展开的办法实现

为了检查编译器对宏所做的替换操作,可以使用gcc -E命令

宏定义需要注意的事情:

  • 宏可以重复定义,但是每次的定义均需相同。 如果需要重新定义一个宏,和原来的定义不同,可以先用#undef取消原来的定义,再重新定义
  • 如果MAX被定义为#define MAX(a, b) (a>b?a:b),那么MAX(i&0x0f, j&0x0f)将会被展开为(i&0x0f>j&0x0f?i&0x0f:j&0x0f)而不是((i&0x0f)>(j&0x0f)?(i&0x0f):(j&0x0f)),由于>结合优先级高于&,因此会出现很难找到的bug
  • do{}while(0)

    内核中宏定义常常写成:
    1
    2
    3
    4
    5
    #define device_init_wakeup(dev,val) \
    do { \
    device_can_wakeup(dev) = !!(val); \
    device_set_wakeup_enable(dev,val); \
    } while(0)
    这里用do { … } while(0)括起来了,如果没有括起来:
    1
    2
    3
    4
    5
    #define device_init_wakeup(dev,val) \
    {device_can_wakeup(dev) = !!(val); \
    device_set_wakeup_enable(dev,val);}
    if (n > 0)
    device_init_wakeup(d, v);
    当使用以下if else语句时,会因多出一个;报错
    1
    2
    3
    4
    if (n > 0)
    device_init_wakeup(d, v);
    else
    continue;

#、##、可变参数

  • 对于函数式宏,#可以将参数转换为字符串(并非简单的加“”,这里已经过转义)
  • ##可以用于上面两种宏,可以将前后两个预处理Token连接成一个预处理Token
  • #define showlist(...) printf(#__VA_ARGS__)
    • …表示可变参数,上面宏展开后为printf("The first, second, and third items.");
    • 在宏定义中,可变参数的部分用__VA_ARGS__表示

C语言:预处理与宏定义详解
http://gls.show/p/96b16a89/
作者
郭佳明
发布于
2022年9月4日
许可协议