您的位置:首页 > 编程语言 > C语言/C++

C语言的预处理器

2015-08-18 08:43 525 查看
预处理器是在编译C程序之前对程序进行编辑的工具,它的行为依赖于以
#
开头的预处理指令。

预处理器在执行指令时可能产生非法的程序,而由于编译的是预处理器编辑过的版本,难以根据错误信息查找错误原因,检查预处理器的输出是解决这个问题的有效途径。
gcc
提供了
-E
选项支持只进行预处理将预处理后的文件显示在控制台上,配合
-o
可以将预处理后的程序存放在指定文件中。使用
-save-temps
选项可以保留
gcc
命令执行过程中的所有中间文件,例如预处理后的结果文件,汇编代码文件和目标文件等。

/**************************************
* prepocessor_result.c               *
*                                    *
* C语言查看预处理输出                *
**************************************/

#define N 10

int main()
{
int n = N;
int m = N + 10;

return 0;
}


使用
-E
选项将预处理结果输出到命令行窗口



使用
-E
-o
选项将预处理结果输出到文件



使用
-save-temps
选项输出所有中间文件,其中
.i
文件为预处理后文件。



预处理指令

预处理指令主要包括以下四类:

宏定义。包括
#define
#undef
指令,前一个指令用以定义一个宏,后一个指令是删除一个宏。

文件包含。
#include
指令,将一个指定的文件包含到程序中。

条件编译。包括
#if
#ifdef
#ifndef
,
#elif
,
#else
#endif
指令,根据条件判断是否将一段文本加入程序。

特殊指令。
#error
#line
#pragma


预处理指令需要满足以下规则:

指令以
#
开始,除注释外,独占一行或多行,不需要使用
;
或其他特殊标记结尾。

指令的符号间可以加入任意数量的空格或横向制表位。

指令总在第一个换行符结束,除非利用
\
明确声明继续。

指令可以出现在程序的任何地方。

宏定义

宏定义可以定义两类宏,一类是简单的宏,一类是带参数的宏。

简单的宏

简单的宏的定义格式如下:

#define 宏名称 替换列表


替换列表是任意C语言标记,包括字符串,字符变量,运算符等等,看可以为空。当预处理遇到宏定义时,会记录该宏名称和它的替换列表,将文件后面的内容中出现宏名称用替换列表替换。宏名称常全部使用大写字母。

简单的宏常用来定义具有明确意义的常量,这样可以增加程序的易读性、易修改性,还可以避免多次输入同一个值不一致错误等。除了用于常量定义,简单的宏还可以通过为C语言的符号添加别名的方式改变C语言语法或发明自己的语言等,重命名类型以及根据宏定义控制条件编译等。

/**************************************
* simple_macro.c                     *
*                                    *
* C语言中的简单宏                    *
**************************************/

#include <stdio.h>

/*改变语法*/
#define LOOP for (;;)
#define BEGIN {
#define END }

/*定义常量*/
#define PRICE 3.52

/*类型重命名*/
#define BOOL int

/*控制条件编译*/
#define PRINT

int main()
{
int i = 1;

LOOP
BEGIN
if (i < 3)
BEGIN
#ifdef PRINT
printf("%d个是%g\n", i, i * PRICE);
#endif
i++;
END else
break;
END

return 0;
}




带参数的宏

带参数的宏定义的格式如下:

#define 宏名称(x1,x2, ...,xn) 替换列表


其中
x1,x2,...,xn
为宏的参数。注意:宏名称和它后面的左括号之间不能有空格。

当预编译器遇到带参数的宏时,记录下这个宏(名称和参数)和替换列表,当在后面文件中遇到
宏名称(y1,y2,...yn)
格式的宏调用时,就用替换列表替换,并将
y1,y2,...yn
分别替换
x1, x2, ... ,xn


带参数的宏避免了函数调用的开销,提供了更快的执行速度,同时宏的参数没有类型,更加通用。然而使用带参数的宏由于 替换列表的插入,使得编译后代码很大,不正确的使用,还可能出现错误的结果。

/**************************************
* parameterized_macro.c              *
*                                    *
* C语言中的带参数的宏                *
**************************************/
#include <stdio.h>

#define FOR(x,y) for(x = 1; x <= y; x++)

int main()
{
int i = 1;
int n = 0;
printf("请输入一个正整数:");
scanf("%d", &n);
int sum = 0;
FOR(i, n)
sum += i;
printf("1到%d的和为%d\n", n, sum);

return 0;
}




用以宏定义的两类运算符
#
##

#
将一个宏的参数转换为字符串常量,只能使用在有参数的宏定义中。
##
运算符将两个记号连在一起成为一个记号。如果其中一个记号是宏参数,那么连接发生在参数替换后。

/**************************************
* preprocessor_operator.c            *
*                                    *
* C语言预处理器的#和##运算符         *
**************************************/

#include <stdio.h>

#define MAX(x,y) printf(#x "和" #y "的最大值为%g\n", x > y ? x : y)
#define MK_ID(n) i##n
#define PRINT(x) printf(#x " = %d\n", x )
#define N 3

int main()
{
float x1 = 0.0f;
float y1 = 0.0f;
printf("请输入两个浮点数:");
scanf("%f%f", &x1, &y1);

MAX(x1, y1);

int MK_ID(1) = 10;
int MK_ID(2) = 20;
PRINT(i1);
PRINT(i2);

return 0;
}




宏的使用规则

宏的替换列表中可以包含对其他宏的调用。

预处理器只替换独立的宏调用,不会替换镶嵌在标识符、字符串常量、字符常量等中的宏名。

宏的作用范围是到宏所在的文件结尾

除非宏的定义相同,否则同一个宏定义不能出现两遍或以上。

可以使用
#undef
取消定义,
#undef
的使用格式为
#undef 宏名称


注意在必要的情况下尽量使用圆括号,否则可能会产生不希望的结果。

可以在替换列表中使用逗号运算符创建更长的宏。

C语言中的预定义宏

名称描述
__LINE__
当前被编译的文件的行号
__FILE__
当前被编译的文件的名称
__DATE__
编译的日期(格式为
mm dd yyyy
)
__TIME__
编译的时间(格式为
hh mm ss
)
__STDC__
如果编译器接受标准C,则值为1
/**************************************
* predefined_macros.c                *
*                                    *
* C语言中的预定义宏                  *
**************************************/

#include <stdio.h>

int main()
{
printf("被编译的文件行数: %d\n", __LINE__);
printf("被编译的文件名称: %s\n", __FILE__);
printf("编译的日期为: %s\n", __DATE__);
printf("编译的时间为: %s\n", __TIME__);
printf("是否接受标准C: %d\n", __STDC__);

return 0;
}




文件包含

#include
指令有两种书写格式,

#include <文件名>


这里包含的头文件是属于C语言自身库的头文件,从系统头文件所在的目录搜索。

#include "文件名"


除系统文件外的其他头文件,先从当前目录搜索,在搜索系统头文件所在的目录,或通过
-I
选项指定搜索文件的路径。

条件编译

#if
#endif

#if
#endif
的使用格式如下:

#if 常量表达式
#endif


当预处理其处理到
#if
指令时,计算常量表达式的值,如果部位0,将与
#endif
中间的行保留在程序中,否则将其从程序中删除。对于没有定义的标识符,
#if
指令会将其当作值为0的宏对待。

defined
运算符

defined
运算符用在预处理器中,判断一个标识符是否是一个被定义的宏,是则返回1,否则返回0。
defined
的使用格式为:

defined(标识符)


#ifdef
指令和
#ifndef
指令

#ifdef
指令用以判断一个标识符是否是一个宏,其使用格式为:

#ifdef 标识符
#endif


等价于
#if
defined
运算符的结合使用:

#if defined(标识符)#endif


#ifndef
指令是测试标识符是否不是一个宏,其使用格式为:

#ifndef 标识符
#endif


等价于

#if !defined(标识符)#endif


#if
,
#ifdef
#ifndef
可以嵌套使用。

#elif
指令和
#else
指令

#elif
#else
可以和
#if
,
#ifdef
#ifndef
结合测试一些列条件,其基本使用格式为:

#ifdef 标识符
#if 常量表达式
...
#elif 常量表达式
...
#else
...
#else
...
#endif


条件编译器的使用情况

编译在多种操作系统间可移植的程序

编写使用不同编译器进行编译的程序

检查宏是否定义,未定义为其提供默认定义

临时屏蔽代码(条件屏蔽)

解决头文件的多次包含问题

/**************************************
* conditional_compile.c              *
*                                    *
* C语言中的条件编译                  *
**************************************/

#include <stdio.h>

#define DEBUG  1
#define LINUX

int main()
{
#if  DEBUG
printf("处于DEBUG模式\n");
#endif

#if defined(DEBUG)
printf("DEBUG宏已被定义\n");
#endif

#ifdef DEBUG
printf("DEBUG宏已经定义\n");
#endif

#ifndef RELEASE
printf("RELEASE宏未被定义\n");
#endif

#if defined(WINDOWS)
printf("当前平台:WINDOWS\n");
#elif defined(DOS)
printf("当前平台是:DOS\n");
#else
#if defined(OS2)
printf("当前平台是:OS2\n");
#elif defined(LINUX)
printf("当前平台是LINUX\n");
#endif
#endif

return 0;
}




特殊指令

#error
指令

#error
的使用格式如下:

#error 消息


如果预处理器遇到一个
#error
指令,会显示一个错误消息。大多数编译器遇到
#error
错误会终止编译。

#error
指令常于条件编译指令一起用于检测编译过程中不应出现的情况。

/**************************************
* special_directives.c               *
*                                    *
* C语言预处理器的特殊指令#error      *
**************************************/
#include <stdio.h>

int main()
{
#ifndef DEBUG
#error DEBUG模式为定义
#endif
return 0;
}




#line
指令

#line
用以改变给程序行编号的方式,使用格式如下:

#line n


使得后面的程序编号从
n+1
开始。

#line
还可以用以使编译器认为后面的程序是从另一个不同名字的文件中读取的,使用格式为:

#line n "文件名"


#line
重要的作用是输出易于调试的信息。

/**************************************
* special_directives_2.c             *
*                                    *
* C语言处理器的特殊命令#line         *
**************************************/

#include <stdio.h>

int main()
{
int i =0;
int n = 0;
int sum = 0;
scanf("%d", &n);
printf("当前行数: %d\n", __LINE__);
#line 500
printf("当前行数: %d\n", __LINE__);

for (i = 1; i <= n; i++)
sum += i;

printf("当前文件: %s, 行数: %d\n", __FILE__, __LINE__);
#line 200 "hello.c"
printf("当前文件: %s, 行数: %d\n", __FILE__, __LINE__);

printf("和为%d\n", sum);
return 0;
}




#pragma
指令

#pragma
要求编译器执行某些特殊操作,其使用格式为:

#pragma 特殊操作命令


#pragma
支持的操作应编译器的不同而不同。若
#pragma
后跟的是无法识别的命令,则编译器忽略该命令,不产生出错信息。
gcc
为与其他编译器兼容提供了多个
#pragma
命令,但它不建议使用
#pragma


参考文献

K.N. King 著,吕秀峰 译. C语言程序设计-现代方法. 人民邮电出版社

https://gcc.gnu.org/onlinedocs/gcc/Pragmas.html

http://blog.163.com/w_fox/blog/static/6233953620148373227198/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: