>>返回AUTOSAR系列文章目录<<
预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器处理代码中的宏命令,在处理完毕后删除所有宏命令
所有的预处理器命令都是以井号#
开头。井号#
必须是第一个非空字符,但是#
和 include
define
之间可以有任意空格
预处理器命令不是C语言,所以不以分号;
结尾
预处理有以下功能:
文本处理相关功能:
① 文件引用和避免重复引用
② 定义宏常量
③ 定义宏函数
④ 代码剪裁
编译器指令相关功能:
⑤ 内存标签
⑥ 嵌入式汇编
文件引用是单纯的文本替换功能,在生成预编译.i文件时,将被引用文件的文本内容替换到#include
语句所在的行
#include
语句可以用来引用任意类型的文本文件,但是最常用的是引用头文件。头文件是扩展名为.h
的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享
有两种类型的头文件:程序员编写的头文件和编译器自带的头文件:
#include
#include "file"
#include < >
引用的是系统自带库,它在系统目录的标准列表中搜索名为 file 的文件
#include " "
引用的是用户自定义库。它在包含当前文件的目录中搜索名为 file 的文件
文件引用无论引用了多少层,都会被替换。预处理器首先替换掉当前#include
语句,如果替换后出现新的#include
语句,则再次替换,直到全部#include
语句替换完成
复杂的嵌套引用是,必然会产生一些头文件被多次引用。头文件被多次引用在预处理时不报错,但是在代码编译时会报错
为避免重复引用,通常会引用每个头文件都定义一个宏常量,如果宏常量已经被定义,则不再引用该头文件
// my_example.h文件
#ifndef _MY__EXAMPLE_H //如果_MY__EXAMPLE_H没被定义,则执行#endif之前的内容
# define _MY__EXAMPLE_H
//code
#endif
这样,my_example.h文件只会被引用一次
宏定义常量由三部分组成,第一部分是#define
本身,第二部分是选定的缩写,也被称为宏。第三部分称为替换文本。从宏变成替换文本的过程称为宏展开。
#define 宏 替换文本
注意,无论是宏还是替换文本,都是纯文本。宏定义只负责把文本换成文本,不会进行任何计算分析。怎样理解文本是程序进程中进行的
注意,宏不能与代码中出现的任何标签的一部分重名,否则将会直接将其替换掉
所以宏要全部大写,并且命名要长,不要单独使用ON,OFF,UP,DOWN,RUN这些常用词汇
但是“ ”
内的文本不会被认为是宏:
/***** 示例程序 *****/
#include
#define TWO 2
int main()
{
printf("TWO = %d", TWO);
return 0;
}
/***** 示例结果 *****/
TWO = 2
#define
常用来定义常量和字符串常量,例如:
#define PI 3.1415926
#define FILE_PATH E:\folder1\1.txt
#define
中还可以包含其他宏:
#define TWO 2
#define Four TWO*TWO
#define
也可以用来替换C表达式。例如:
#include
#define PX printf("X is %d.\n", x)
int main()
{
int x = 2;
PX;
return 0;
}
#define
可以创建类似函数的类函数宏,可以带一个或多个参数。类函数宏名也要全大写
#include
#define SQUARE(X) X * X
int main()
{
int x, y, z;
x = SQUARE(2); // x = 2 * 2 = 4
y = SQUARE(2+2); // y = 2 + 2 * 2 + 2 = 8
z =4/SQUARE(2); // Z = 4 / 2 * 2 = 4
return 0;
}
示例中X
可以被任何文本替换,但是替换后的文本计算优先级难以控制,所以在使用中最好将替换文本里的变量都加上()
,整个替换体也加上()
。
#include
#define SQUARE(X) ((X) * (X))
int main()
{
int x, y, z;
x = SQUARE(2); // x = ((2) * (2)) = 4
y = SQUARE(2+2); // y = ((2 + 2) * (2 + 2)) = 16
z =4/SQUARE(2); // Z = 4/((2) * (2)) = 1
return 0;
}
有参数的宏支持多个参数:
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
编译器不会识别宏运算符,它们会在预处理时被执行。宏运算符仅允许出现在宏的替换体中。
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符\
:
#define MSG "Hello \
world"
字符串常量化运算符#
只会出现在有参数的宏当中,且被放在宏参数前面。
在“”
中的宏无法被替换,#
用于解决这个问题。注意,#X
本身依然不能放在“”
中
#X == "X"
/***** 示例程序 *****/
#include
#define MSG(a,b) printf("I am " #a",I am "#b)
int main(void)
{
MSG(chuhe,20);
return 0;
}
/***** 示例结果 *****/
I am chuhe,I am 20
标记粘贴运算符##
只会出现在有参数的宏当中,且被放在宏参数前面,用于将两个标识符合并为一个标识符。
#define MK_ID(i) n##i
int MK_ID(1), MK_ID(2); // 等效于 int n1,n2;
在大型工程中,通常会写全功能的代码,但某些功能在本次项目不需要,则可以使用代码剪裁,可以让代码中一些不执行部分在编译时被删除,节省代码容量
//my_example.h
#define STD_ON 0
#define STD_OFF 1
#define PROJECT1_SWITCH STD_ON
//my_example.c
#include "my_example.h"
#if(PROJECT1_SWITCH == STD_ON)
//project1 code
#endif
project1 code
部分代码只会在#define PROJECT1_SWITCH STD_ON
时才编译,在#define PROJECT1_SWITCH STD_OFF
时不参与编译
>>返回AUTOSAR系列文章目录<<