C语言学习笔记:预处理器

预定义符号

预处理器定义了一些符号,它们的值是一些常量。

|:—:|:—:|
|符号|含义|
|_FILE_|进行编译的源文件名|
|_LINE_|文件当前行的行号|
|_DATE_|文件被编译的日期(年月日)|
|_TIME_|文件被编译的时间(时分秒)|
|_STDC_|如果编译器支持ANSI C,它就是1,否则未定义|

define

首先,这是它的正式定义:

1
#define name stuff

这样,每当有name出现在这一行的后面时,预处理器就会把它替换成stuff

#define提供了一个机制:可以将参数替换到文本中去。这种实现被称作宏。下面是它的声明方式:

1
#define name(parameter-list) stuff

parameter-list,也就是参数列表,是一个由逗号分隔,每一项都可能出现在stuff中的列表。此处的括号必须与name紧邻。下面是一个实例:

1
#define SQUARE(x) x * x

如果把SQUARE(5)放在随后的代码中,预处理器就会把它替换成5 * 5。但是,如果是SQUARE(5+1)呢?很显然是5+1 * 5+1,不是我们预期的结果。要修复这个问题,就把宏改成这样:

1
#define SQUARE(x) (x)*(x)

那如果宏定义中间的符号是加号而非乘号呢?结果也非预期。我们又要改一改了:

1
#define ADD(x) ((x)+(x))

这样就安全了吗?并不。如果x是某种值会改变的表达式(例如x=getchar(),或者设想你写了一个用来比较大小的宏MAX(a,b),然后如此调用:MAX(b++,c++)),那么替换后,两个x的值也不会相等。也就是说,作为宏参数的表达式会被多次求值

那么为什么要使用宏呢?有三点原因:宏可以做到函数做不到的事;并且,宏的执行效率要高于函数。

  • 宏比函数的效率高:因为调用函数时,需要为函数分配栈空间等,所以多少会有时间损耗。在某些场景下更是明显。宏唯一的开销是编译时的时间变长,程序体积变大。

  • 宏能做到更多:例如这个:

1
2
#define MALLOC(n, type) \
((type*)malloc((n)*sizeof(type)))

类型是无法作为函数参数进行传递的。

  • 宏与类型无关:例如这个宏:
1
#define MAX(a,b) ((a)>(b)?(a):(b))

它与类型无关。如果用函数来实现,那么就需要很多不同版本的函数了。

这里注意,宏名一般用大写字母表示。这是约定,为了区分它和函数而设定。因为它和真正的函数还是有着不同之处的。

注意

这里有两点注意事项:一个是,可以用反斜杠\来让宏换行书写而不间断;另一个是,注意宏定义末尾并没有加分号,这是因为我们希望在书写时,可以像调用函数一样调用它,而不会因为没注意到重复分号,而在一些场合(如if-else)中将两条语句错当成一条,从而造成错误。

undef

用于移除一个现存的宏定义:

1
#undef name

命令行定义

编译时,可以在编译选项中定义宏。

1
2
3
4
5
6
#include <stdio.h>

int main(void)
{
int array[ARRAY_SIZE];
...

源码中并没有给出ARRAY_SIZE的定义,所以我们必须在编译时指定。

通用格式为:

1
2
-Dname
-Dname=stuff

所以我们应该这样给出它的定义:

1
gcc main.c -DARRAY_SIZE=100

条件编译

1
2
3
4
5
6
7
8
#if constant-expression
statements
/* #elif可选 */
#elif constant-expression
statements
#else
statements
#endif

constant-expression,即常量表达式,意思是说要么它是一个字面值常量(比如1),要么就是用define定义的符号。

此时,预处理器就会根据这几个常量表达式来对源代码选择性地编译了。在进行debug时尤为有用。定义宏DEBUG,若值为1则编译一些测试时才会用的语句;否则只编译其他语句。

同时它还有个较常用的指令:是否被定义

1
2
3
4
5
6
7
//这几条都是等价的
#if defined(symbol)
#ifdef symbol
//还可以用逻辑运算
#if !defined(symbol)
//和下面这条等价
#ifndef symbol

上面所说的那些条件编译指令也支持嵌套。

include

#include表示将后面跟随的文件的所有内容复制并替换这一行语句。它有两种形式:

1
2
3
//这表示函数库文件
#include <stdio.h>
//这表示同目录文件。如果找不到,则会在函数库目录查找

其他

包含#error#line#progma等。不一一介绍了。


宏的内容基本就是这些了。

作者

xeonds

发布于

2021-11-11

更新于

2024-05-13

许可协议

评论