当前位置 博文首页 > CW_qian的博客:8月22日笔记C语言基础(补3)宏定义与条件编译1
预处理
????????在C语言程序源码中,凡是以井号(#)开头的语句被称为预处理语句,这些语句严格意义上并不属于C语言语法的范畴,它们在编译的阶段统一由所谓预处理器(cc1)来处理。所谓预处理,顾名思义,指的是真正的C程序编译之前预先进行的一些处理步骤,这些预处理指令包括:
gcc example.c -o example.i -E
宏的概念
????????宏(macro)实际上就是一段特定的字串,在源码中用以替换为指定的表达式。例如:
#define?PI 3.14
此处,PI 就是宏(宏一般习惯用大写字母表达,以区分于变量和函数,但这并不是语法规定,只是一种习惯),是一段特定的字串,这个字串在源码中出现时,将被替换为3.14。例如:
int?main()
{
????printf("圆周率: %f\n", PI);
????// 此语句将被替换为:printf("圆周率: %f\n", 3.14);
}
无参宏
????????无参宏意味着使用宏的时候,无需指定任何参数,比如:
#define?PI ?????????3.14
#define?SCREEN_SIZE 800*480*4
int?main()
{
????// 在代码中,可以随时使用以上无参宏,来替代其所代表的表达式:
????printf("圆周率: %f\n", PI);
????mmap(NULL, SCREEN_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, ...);
}
注意到,上述代码中,除了有自定义的宏,还有系统预定义的宏:
// 自定义宏:
#define?PI ?????????3.14
#define?SCREEN_SIZE 800*480*4
// 系统预定义宏
#define?NULL ((void *)0)
#define?PROT_READ 0x1 /* Page can be read. ?*/
#define?PROT_WRITE 0x2 /* Page can be written. ?*/
#define?MAP_SHARED 0x01 /* Share changes. ?*/
宏的最基本特征是进行直接文本替换,以上代码被替换之后的结果是:
int?main()
{
????printf("圆周率: %f\n", 3.14);
????mmap(((void?*)0), 800*480*4, 0x1|0x2, 0x01, ...);
}
带参宏
带参宏意味着宏定义可以携带“参数”,从形式上看跟函数很像,例如:
#define?MAX(a, b) ??a>b ? a : b
#define?MIN(a, b) ??a<b ? a : b
以上的MAX(a,b) 和 MIN(a,b) 都是带参宏,不管是否带参,宏都遵循最初的规则,即宏是一段待替换的文本,例如在以下代码中,宏在预处理阶段都将被替换掉:
int?main()
{
????int?x = 100, y = 200;
????printf("最大值:%d\n", MAX(x, y));
????printf("最小值:%d\n", MIN(x, y));
????// 以上代码等价于:
????// printf("最大值:%d\n", x>y ? x : y);
????// printf("最小值:%d\n", x<y ? x : y);
}
带参宏的副作用
????????由于宏仅仅做文本替换,中间不涉及任何语法检查、类型匹配、数值运算,因此用起来相对函数要麻烦很多。例如:
#define?MAX(a, b) a>b ? a : b
int?main()
{
????int?x = 100, y = 200;
????printf("最大值:%d\n", MAX(x, y==200?888:999));
}
直观上看,无论 y 的取值是多少,表达式 y==200?888:999 的值一定比 x 要大,但由于宏定义仅仅是文本替换,中间不涉及任何运算,因此等价于:
????????printf("最大值:%d\n", x>y==200?888:999?? x : y==200?888:999);
可见,带参宏的参数不能像函数参数那样视为一个整体,整个宏定义也不能视为一个单一的数据,事实上,不管是宏参数还是宏本身,都应被视为一个字串,或者一个表达式,或者一段文本,因此最基本的原则是:
#define?MAX(a, b) ((a)>(b) ? (a) : (b))
无值宏定义
????????定义无参宏的时候,不一定需要带值,无值的宏定义经常在条件编译中作为判断条件出现,例如:
#define?BIG_ENDIAN
#define?__cplusplus
条件编译
#define?A 0
#define?B 1
#define?C 2
#if?A
????... // 如果 MACRO 为真,那么该段代码将被编译,否则被丢弃
#endif
// 二路分支
#if?A
????...
#elif?B
????...
#endif
// 多路分支
#if?A
????...
#elif?B
????...
#elif?C
????...
#endif
// 单独判断
#ifdef?MACRO
????...
#endif
// 二路分支
#ifdef?MACRO
????...
#else
???...
#endif
// 单独判断
#ifndef?MACRO
????...
#endif
// 二路分支
#ifndef?MACRO
????...
#else
???...
#endif
条件编译的使用场景
控制调试语句:在程序中,用条件编译将调试语句包裹起来,通过gcc编译选项随意控制调试代码的启停状态。例如:
????????gcc example.c -o example -DMACRO
以上语句中,-D意味着 Define,MACRO 是程序中用来控制调试语句的一个宏,如此一来就可以在完全不需要修改源代码的情况下,通过外部编译指令选项非常方便地控制调试信息的启停。
选择代码片段:在一些大型项目中(例如 Linux 内核),某个相同功能的模块往往有不同的实现,需要用户根据具体的情况来“配置”,这个所谓的配置的过程,就是对代码中不同的宏的选择的过程。
例如:
#define?A 0 ?// 网卡1
#define?B 1 ?// 网卡2 ?√
#define?C 0 ?// 网卡3
// 多路分支
#if?A