0%

C语言预处理

文章时效性提示

本文发布于 502 天前,部分信息可能已经改变,请注意甄别。

预处理

预定义符号

1
2
3
4
5
__FILE__      //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的,例如:

1
2
3
4
5
#include <stdio.h>
int main() {
    printf("file: %s\nline: %d\n",__FILE__,__LINE__);
    return 0;
}

打印的结果是:

1
2
file: /Users/miles/Library/Mobile Documents/com~apple~CloudDocs/C/预处理2024_10_29_12_04/预处理2024_10_29_12_04/main.c
line: 10

#define

#define定义标识符

语法:
#define name stuff

使用#define大体上就是完成替换的操作。例如#define reg register之后在程序中写rge int a;register int a;的效果是一致的。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define MAX 100
#define STR "Hello"
int main() {
    int max = MAX;
    printf("%d\n",max);
    printf("%s\n",STR);
    return 0;
}

打印的结果是

1
2
100
Hello

使用#define还可以替换函数,例如:

1
2
3
4
5
6
#include <stdio.h>
#define do_forever for(;;)
int main() {
    do_forever;
    return 0;
}

上面的代码就相当于在主函数中运行for(;;);,即程序死循环。

不要在#define后面加分号;,因为替换后会变成两个分号。

#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)

语法:
#define name(parament-1ist) stuff
parament-list是一个由逗号隔开的符号表,可能出现在stuff中
注意⚠️:parament-list的左括号必须紧邻name。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define SQUARE(X) X*X
int main() {
    int ret = SQUARE(5);
    //上面的代码等价于
    //int ret = 5*5;
    printf("%d ",ret);
    return 0;
}

打印的结果是25。

上面的代码存在一些问题,例如:

1
2
3
4
5
6
7
#include <stdio.h>
#define SQUARE(X) X*X
int main() {
    int ret = SQUARE(5+1);
    printf("%d ",ret);
    return 0;
}

打印的结果不是期望的36,而是11,这是运算优先级的问题导致的,即计算顺序是:5+1*5+1=11
要解决这个问题,可以把宏替换进去的内容加上括号,即(5+1)*(5+1)=36
下面是更正后的代码:

1
2
3
4
5
6
7
#include <stdio.h>
#define SQUARE(X) (X)*(X)
int main() {
    int ret = SQUARE(5+1);
    printf("%d ",ret);
    return 0;
}

上面的代码也存在一些问题,例如:

1
2
3
4
5
6
7
8
#include <stdio.h>
#define Double(X) (X)+(X)
int main() {
    int a = 5;
    int ret = 10 * Double(a);
    printf("%d ",ret);
    return 0;
}

预期的结果是100,然而打印的结果是55,这也是由于运算优先级的问题,程序的计算方法是:10 * (5) + (5) = 55
为了解决这个问题,我们在X的最外层加上括号,即10 * ( (5) + (5) ) = 100
更正后的代码:

1
2
3
4
5
6
7
8
#include <stdio.h>
#define Double(X) ((X)+(X))
int main() {
    int a = 5;
    int ret = 10 * Double(a);
    printf("%d ",ret);
    return 0;
}

也就是说,有关数值表达式求值的宏定义都应该加上括号,避免出现求值错误

#define 替换规则

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
    注意:
  4. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

#和##

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#define PRINT(X) printf("the value of "#X" is %d\n",X)
int main()
{
    int a = 10;
    int b = 20;
    PRINT(a);
    PRINT(b);
    return 0;
}

打印的结果是:

1
2
the value of a is 10
the value of b is 20

##可以把位于它两边的符号合成一个符号。

1
2
3
4
5
6
7
#include <stdio.h>
#define CAT(X,Y) X##Y
int main()
{
    int Hello = 1234;
    printf("%d\n",CAT(Hel, lo));
}

打印的结果是1234
注意⚠️:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

带有副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:

1
2
x+1;//没有副作用
x++;//带有副作用

使用带副作用的宏参数时一定要小心,建议不要写带副作用的宏参数

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main()
{
    int a = 10;
    int b = 11;
    int max = MAX(a++,b++);
    //int max = ((a++) > (b++) ? (a++) : (b++));
    printf("%d ",max);
    printf("%d ",a);
    printf("%d\n",b);
}

打印的结果是12 11 13

宏和函数对比

宏经常用于简单的运算,例如比较两个数的大小。
define MAX(X,Y) ( (X) > (Y) ? (X) : (Y) )
在执行小型运算过程中,宏比函数在程序的规模和速度方面更好,此外,宏是类型无关的。
命名习惯:宏名大写,函数名不要全部大写

#undef:移除宏定义

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define MAX 100
int main()
{
    printf("%d\n",MAX);
#undef MAX
    printf("%d\n",MAX);
    //程序无法正常运行,这里MAX是未定义的
}

如果现存的一个名字需要被重新定义,使用#undef来移除宏定义

条件编译

使用条件编译指令使一条或者一段语句在程序编译时放弃编译。

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
    printf("hello");
#if 1
    printf(" world\n");
#endif
}

#if后面的表达式如果为真,后面的表达式参与编译,为假,不参与编译。

多分支的条件编译

语法:

1
2
3
4
5
6
7
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

举个例子:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
#if 1
    printf("1");
#elif 1
    printf("2");
#else
    printf("3");
#endif
}

上例中,如果if后面的语句为真,打印1,
if后面为假且elif后面为真,打印2,
if和elif后面都为假,打印3。

判断是否被定义

1
2
3
4
5
6
7
8
9
//定义了执行
#if defined(symbol)
    //...
#endif

//没有定义执行
#if !defined(symbol)
    //...
#endif

通过判断symbol是否被定义来决定是否执行语句。

1
2
3
4
5
6
7
8
#include <stdio.h>
#define debug
int main()
{
#if defined(debug)
    printf("debug");
#endif
}

上例,如果定义了debug,则打印debug。

嵌套指令

上述的条件编译指令可以嵌套使用。