C语言的预处理器
2015-08-18 08:43
525 查看
预处理器是在编译C程序之前对程序进行编辑的工具,它的行为依赖于以
预处理器在执行指令时可能产生非法的程序,而由于编译的是预处理器编辑过的版本,难以根据错误信息查找错误原因,检查预处理器的输出是解决这个问题的有效途径。
使用
使用
使用
宏定义。包括
文件包含。
条件编译。包括
特殊指令。
预处理指令需要满足以下规则:
指令以
指令的符号间可以加入任意数量的空格或横向制表位。
指令总在第一个换行符结束,除非利用
指令可以出现在程序的任何地方。
替换列表是任意C语言标记,包括字符串,字符变量,运算符等等,看可以为空。当预处理遇到宏定义时,会记录该宏名称和它的替换列表,将文件后面的内容中出现宏名称用替换列表替换。宏名称常全部使用大写字母。
简单的宏常用来定义具有明确意义的常量,这样可以增加程序的易读性、易修改性,还可以避免多次输入同一个值不一致错误等。除了用于常量定义,简单的宏还可以通过为C语言的符号添加别名的方式改变C语言语法或发明自己的语言等,重命名类型以及根据宏定义控制条件编译等。
其中
当预编译器遇到带参数的宏时,记录下这个宏(名称和参数)和替换列表,当在后面文件中遇到
带参数的宏避免了函数调用的开销,提供了更快的执行速度,同时宏的参数没有类型,更加通用。然而使用带参数的宏由于 替换列表的插入,使得编译后代码很大,不正确的使用,还可能出现错误的结果。
用以宏定义的两类运算符
预处理器只替换独立的宏调用,不会替换镶嵌在标识符、字符串常量、字符常量等中的宏名。
宏的作用范围是到宏所在的文件结尾
除非宏的定义相同,否则同一个宏定义不能出现两遍或以上。
可以使用
注意在必要的情况下尽量使用圆括号,否则可能会产生不希望的结果。
可以在替换列表中使用逗号运算符创建更长的宏。
这里包含的头文件是属于C语言自身库的头文件,从系统头文件所在的目录搜索。
除系统文件外的其他头文件,先从当前目录搜索,在搜索系统头文件所在的目录,或通过
当预处理其处理到
等价于
等价于
编写使用不同编译器进行编译的程序
检查宏是否定义,未定义为其提供默认定义
临时屏蔽代码(条件屏蔽)
解决头文件的多次包含问题
如果预处理器遇到一个
使得后面的程序编号从
https://gcc.gnu.org/onlinedocs/gcc/Pragmas.html
http://blog.163.com/w_fox/blog/static/6233953620148373227198/
#开头的预处理指令。
预处理器在执行指令时可能产生非法的程序,而由于编译的是预处理器编辑过的版本,难以根据错误信息查找错误原因,检查预处理器的输出是解决这个问题的有效途径。
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/
相关文章推荐
- 【C语言连载二】--------选择结构、循环结构、跳转语句(附几个例子)
- 【C语言连载一 】----------C语言基础
- c++ string
- libstdc++.so.6: version `GLIBCXX_3.4.15' not found
- 【8-17】c++学习笔记01
- 关于C++函数调用的那些事儿
- c语言 malloc小案例1
- google C++编程风格指南之头文件的包含顺序
- google C++编程风格指南之头文件的包含顺序
- C++零基础教程,游戏开发入门
- C/C++获取本地时间常见方法
- C/C++获取本地时间常见方法
- 【C++】sprintf的用法详解 (打印成各种格式的字符串)
- HDOJ-1874-畅通工程续(最短路)
- C++中public、protected及private区别
- Effective C++ 条款3 尽可能用const
- C++的struct和union
- C++的struct和union
- c语言 typedef的用法
- printf中的使用(c语言)