C语言的预处理器

来源:互联网 发布:淘宝宝贝链接地址提取 编辑:程序博客网 时间:2024/05/21 17:40

预处理器是在编译C程序之前对程序进行编辑的工具,它的行为依赖于以#开头的预处理指令。

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

/************************************** * prepocessor_result.c               * *                                    * * C语言查看预处理输出                * **************************************/#define N 10int 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 PRINTint 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 3int 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;}

C语言的预定义宏

文件包含

#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

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

#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 LINUXint 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;}

error

#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;}

#line

#pragma指令

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

#pragma 特殊操作命令

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

参考文献

  1. K.N. King 著,吕秀峰 译. C语言程序设计-现代方法. 人民邮电出版社
  2. https://gcc.gnu.org/onlinedocs/gcc/Pragmas.html
  3. http://blog.163.com/w_fox/blog/static/6233953620148373227198/
0 0
原创粉丝点击