最近做嵌入式的系统移植,牵涉到C里面一些以前没有注意太多的东西,于是又将C的一些更为细节的东西看了一遍。
C程序的编译过程可以分为以下4个步骤:
- 预处理
- 编译
- 汇编
- 链接
这篇主要总结关于预处理的细节问题。
在编译程序之前,预处理器根据程序中的预处理指令,来替换程序中的内容。
宏定义
#define
类对象宏
#include<stdio.h>
#define AS 2
#define HW "Hello World."
#defien ANS AS*AS
#defien PX printf("X is %d.\n",x)
int main()
{
int x;
PX;
x = ANS;
PX;
printf("%s\n",HW);
printf("ANS: HW\n");
return 0;
}
运行结果:
X is 2.
X is 4.
Hello World.
ANS: HW
从上面的示例可以看出,每个#define指令由三部分构成:第一个部分是#define指令本身;第二个部分是所选择的缩略语,被称为宏,宏的中间不能有空格;第三个部分被称为替换列表或者主体。预处理器会用主体替换程序中宏的实例,这个过程称为宏展开。该指令的作用域由定义出现位置到文件结束。
从技术层面看,预处理程序将宏的主体当做语言符号类型字符串,而不是字符型字符串。
#define FOUR 2*2 //有一个语言符号,即2*2
#define FOUR 2 * 2 //有三个语言符号,即2、*和2
类函数宏
通过使用参数,可以创建外形和作用都和函数相似的类函数宏。宏的一个或多个参数用圆括号括起来,随后这些参数出现在替换文本部分。这里有一个关于类函数宏经典的示例。
#include <stdio.h>
#define SQUARE (X) X*X
#define PR (X) printf ("The result is %d.\n",X)
int main()
{
int x = 4;
int ans;
PR(x);
ans = SQUARE(x);
PR(ans);
PR(SQUARE(x+2));
PR(100/SQUARE(x));
PR(SQUARE(++x));
return 0;
}
运行结果:
The result is 4.
The result is 16.
The result is 14.
The result is 100
The result is 30
在运行结果可以看到,前两个结果是我们期待的,但是后面三个却有点莫名其妙。这就是类函数宏的使用过程中必须注意的地方,由于宏定义的结果是直接进行文本的替换,所以上述程序的替换结果实际是:
SQUARE(x) ——— x*x
SQUARE(x+2) ——— x+2*x+2
100/SQUARE(x) ——— 100/2*2
SQUARE(++x) ——— ++x*++x
这样就难怪会出现前面那些结果了,因此在类函数宏中应该使用必需的足够多的圆括号来保证运算的优先级,前面函数的宏就最好定义为:
#define SQUARE (X) ((X) * (X))
但是就是这样仍然无法避免上面的最后一个错误,因此在宏的参数中应该避免使用自加或自减运算符。
#运算符
如果确实想要在字符串中包含宏参数,那么就可以使用#运算符,它可以把语言符号转化为字符串。示例如下:
#include<stdio.h>
#define SQUARE (X) printf("The result of "#X" is %d.\n",((X)*(X)))
int main()
{
int y = 4;
SQUARE (y);
SQUARE (2+4);
return 0;
}
运行结果:
The result of y is 25.
The result of 2+4 is 36.
调用宏时,用"y"和"2+4"来代替#X。
##运算符
##运算符可以将两个语言符号组合成一个语言符号。例如:
#define Name (n) x ## n
Name (3)
宏展开为
x3
#undef
有宏定义指令,自然就有取消宏定义的指令。即
#undef 宏名
C标准指定的一些预定义宏
文件包含
#include
预处理器在发现#include指令后,就会寻找后跟的文件并把这个文件的内容包含到当前文件中。#include有两种使用形式:
#include<stdio.h> —— 搜索系统目录
#include"my.h" —— 搜索当前工作目录
条件编译
#ifdef 宏名
#ifndef 宏名
#else
#endif (若#ifdef和#ifndef,则其必须存在)
#if
#elif
使用以上的指令设置条件编译,告诉编译器,根据编译时的条件选择代码块。使用#ifdef主要用于编写大量跨平台的代码时,需根据不同情况进行编译。#ifndef则主要用于头文件的编写中,防止头文件被多次包含。如
#ifndef THINGS_H_
#define THINGS_H_
/*头文件的其余部分*/
#endif
其余指令
#line
用于重置由_ _LINE_ _和_ _FILE_ _宏报告的行号和文件名。
#line 1000 //把当前行号置为1000
#line 10 "my.c" //把行号置为10,文件名重置为my.c
#error
使预处理器发出一条错误消息,消息包含指令中的文本,可能的话编译过程应该中断。
#if _ _STDC_VERSION_ _ != 199901L
#error Not C99
#endif