Skip to content

预处理器

预定义的符号

C
__FILE__ 进行编译的源文件名
__LINE__ 当前的行数
__DATE__ 编译时候的日期
__TIME__ 时间
__STDC__ 编译器是否遵循ANSI C
C
  1 #include <stdio.h>                                                                    
  2 
  3 int main(void){
  4     printf("file %s\n", __FILE__);
  5     printf("line %d\n", __LINE__);
  6     printf("date %s\n", __DATE__);
  7     printf("time %s\n", __TIME__);
  8     printf("stdc %d\n", __STDC__);
  9 
 10 
 11     return 0;
 12 }
result :
file main.c
line 5
date Jul 28 2022
time 18:08:31
stdc 1

#define

使用define你可以把文本替换到程序之中

如果define很长可以用 \ 来连接不同的行

C
#define DEBUG_PRINT  printf("File %s line: %d:" \
						" x=%d, y=%d, z= %d", \
						__FILE__, __LINE__, x, y, z)

  6 int main(void)
  7 {
  8     int x = 2;
  9     int y = 1;
 10     int z = 4;
 11     DEBUG_PRINT;
 12     return 0;
 13 }

在这个宏定义调用的时候加了一个分号,所以在定义的时候最好不要加分号,如果插入宏定义的地方只能插入一条语句就会出问题,最好定义为函数

C
#define PROCESS_LOOP  \
			for(i  = 0; i < 10; i++)  \
			{						\
    			sum += i;			 \
				if(i>0)				\
                    prod *= i;		  \
			}

C
#define name(parameter-list) stuff

parameter-list是一个用逗号分割的符号列表,括号左边必须和name紧邻

在使用的时候会原封不动的进行转换,所以要注意是不是会与周围的语句产生不同的含义

尽量少使用宏改变C语言的执行方式,让他看起来像别的语言的实现,会导致代码阅读不方便

define替换

在执行#define的替换的时候要涉及几个步骤

  • 宏调用的时候检查是不是有地方包含#define定义的符号,有的话替换
  • 替换的文本插入原来的位置
  • 再次扫描

所以宏参数可以包含其他的宏符号,但是不能进行递归

在检查的时候不会检查字符串内部

插入到字符串的方法:

  • 利用临近的字符串自动连接
C
  1 #include <stdio.h>                                                                    
  2 
  3 #define PRINT(PORMAT, VALUE)            \
  4         printf("The value is "PORMAT"\n", VALUE)
  5 
  6 int main(void)
  7 {
  8     PRINT("%d", 23);
  9 
 10     return 0;
 11 }
  • 适用预处理器把宏定义转换为字符串 #argument 被翻译成"argument"
C
  1 #include <stdio.h>
  2 
  3 #define PRINT(FORMAT,VALUE)     \
  4         printf("The value of "#VALUE" is "FORMAT"\n", VALUE)
  5 int main(void)                                                                        
  6 {
  7     int x = 1;
  8     PRINT("%d", x+3);
  9     return 0;
 10 } 
result:
	The value of x+3 is 4
  • ## 构建一种不同的任务,他把两个符号连接到一起,构成一个新的符号
C
  1 #include <stdio.h>                                                                    
  2 
  3 #define ADD_TO_SUM(sum_number, value)       \
  4     sum ## sum_number += value
  5 
  6 int main(void)
  7 {
  8     int sum2 = 1;
  9     ADD_TO_SUM(2, 45);
 10     printf("%d\n", sum2);
 11 }

宏与函数

宏频繁的使用于简单的运算,原因

  • 从函数来回的时间可能比计算本身更长
  • 函数必须声明为固定的类型,宏可以作用于不同的类型

缺点

  • 使用的宏最后都会插入到程序之中,大量使用会使得程序变长

有一些任务函数不能实现

C
#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))

有副作用的宏参数

如果宏参数在宏定义中出现多次,就有可能会产生副作用

C
#define MAX(a, b) ((a) > (b) ? (a) : b)
x = 5;
y = 8;
z = MAX(x++, y++);//这里较大的数进行了两次相加
//这个时候 x=6 y=10 z=9

类似的还有getchar函数

命名约定

由于使用的规则不同,最好让程序员知道的使用的是宏还是函数,但是两者的使用方法相同

  • 所有的字母大写

不同之处

属性#define宏函数
代码长度使用时会插入代码,程序大幅度增加代码只出现在一个地方,每次都到同一个地方调用
执行速度更快存在函数的调用返回
操作符优先级宏参数的求值是在周围的环境之中,会受到环境的影响,除非加上括号函数的参数在函数调用的时候求值,求值的结果更容易预测
参数求值参数每次使用于宏定义的时候都会重新求值,由于多次求值导致结果不可预测在被调用之前只求值一次,多次调用不会多次求值
参数类型宏与类型无关,只要参数合法就可以使用函数的参数和类型有关,类型不同要用不同的参数

#undef

用于移除一个宏定义

命令行定义

许多编译器允许在命令行之中定义符号

  • 使用-D命令可以在编译的时候创建变量
C
-Dname
-Dname=stuff
C
//main5.c
  1 #include <stdio.h>                                                                    
  2 
  3 int main(void)
  4 {
  5     int test[ARRAY_SIZE];//在之后才决定大小
  6     printf("%d\n", sizeof(test));
  7     return 0;
  8 }

gcc -DARRAY_SZIE=5 main5.c
result :
20
  • 使用-U会使得对应的变量被忽略
C
-Uname//实测不好用,只有在传参数定义宏的时候才会起作用

条件编译

C
#if constant-expression
	stamtments
#endif

constant-expression由预处理器求值,如果求得的值是真的那就执行

C
#if constant-expression
	stamtments
#elif constant-expression
	stamtments
#else
    stamtments
#endif

示例

C
#define DEBUG 1
#if DEBUG
printf("x=%d\n", x);
#endif

在早期的编译器中没有elif,但是可以使用if的嵌套来完成复杂的程序

是否被定义

C
#if define(symbol)
#ifdef symbol
#if !define(symbol)
#ifndef symbol

文件包含

预处理的时候删除这一条指令,并把文件中的内容取而代之

在这个时候整个头文件都会被编译一次,所以最好只放函数的声明,只存放必要的文件

使用#ifndef防止重复编译

嵌套文件包含

标准要求编译器最少支持8层以上的头文件包含

  • 会导致不能判断真正的依赖关系,在编译的时候造成困难
  • 头文件多次包含
C
#ifndef __AAAA
#define __AAAA

#endif

其他指令

  • #error
C
#error text of error message//在编译的时候发出一个错误信息

示例

C
1 #error "111111"                                                                       
  2 int main(void)
  3 {
  4     return 0;
  5 }

result:
main7.c:1:2: error: #error "111111"
    1 | #error "111111"
      |  ^~~~~
  • #line
C
#line number "string" //line行号,string可选文件名更改

示例

C
  1 #include <stdio.h>                                                                    
  2 
  3 #line 100 "test110.c"
  4 
  5 int main(void){
  6     printf("file %s\n", __FILE__);
  7     printf("line %d\n", __LINE__);
  8     return 0;
  9 }
result:
file test110.c
line 103

常用于把其他语言转换为C语言的代码之中,错误信息是源文件的行号和文件名

  • #program 编译器处理一些设置,根据编译器的不同进行改变
  • # 单独一行,不加任何东西,用来突出显示其他行,在编译的时候会直接被删除