今天在网上看到一个关于#error的预处理问题,很是困惑,没想到还有这种预处理命令,所以从网上系统得看了一下c语言的预处理命令!现总结如下,如有理解错误,欢迎大家批评指正!
首先,我们都知道整个程序从无到有的几个关键步骤:编译->链接->运行。其中,编译处于第一阶段,同时源代码编译和符号表的生成都在这个阶段。而且编译阶段再细分的话,分两个步骤:预编译->编译。我们所要关注的问题就是在预编译的阶段,在这个阶段,预编译器会重新扫描整个源文件代码,根据其中的预处理命令,重新排布源代码(删除一些无用的或是兼容性的代码),最后将产生的新的源代码文件,供给编译器进行编译(语言转化为机器码)。
预处理阶段,预处理器除了相应的源代码的转化工作外,还会删除多余的空白字符和代码注释。预处理器主要完成以下基本工作:包含其他源文件,宏定义,根据条件决定要编译的代码。下面单独的看一下,每个预编译命令所实现的预处理功能。
1. #include命令
#include命令主要是将其他源文件包含到现有的编译文件中,因为在其他.h文件中,存在我们需要使用的变量和函数声明。#include预处理命令后可以加入两种格式的.h文件,如下所示:
#include <stdio.h>
#include "my.h"
其中第一种方式,预处理器会在编译器自带的或外部库的头文件中搜索被包含的头文件。而第二种方式下,预处理器会在当前编译目录下,搜索要找的文件,如果找不到,则会搜索编译器自带的头文件。
2. #define 命令
#define主要产生宏定义命令,预处理器会扫描整个源文件,将有宏定义的地方全部替换成相应的表达式!主要的应用方式有两种,如下所示:
#define SECOND_PER_YEAR (60*60*24*365)UL
#define MIN(A,B) ((A) <= (B)? (A) : (B))
其中方式一中,预处理器会帮你计算出表达式的值,并且将整个源文件中,所有遇到的SECOND_PER_YEAR替换成计算出的表达式的值。在第二种方式中,属于带参数的宏的用法,类似于函数,不过宏只是简单的替换,并没有函数的参数传递过程和返回值。在第二种方式中,我们要注意以下几点:
a) 注意在参数宏的定义时,参数要加上括号,这样能避免其他不必要的歧义发生。
b) 三重条件操作符?:使得编译器能够产生比if-else更优化的代码,了解这个方法很重要。
c) 在使用带参数的宏时,有时会产生一些副作用,导致程序的运行方向,并不是预料的那样。
3. #undef
取消已经定义的宏,这个宏并不是很常用。
4#ifdef, #ifndef, #elif, #endif
以上这几个预编译命令都是条件编译命令,我们只以其中一个命令来举例说明,其他的命令与其类似。
#ifndef MY_H
code
#endif MY_H
条件编译的主要目的,是决定是否将其内的code保留在源文件中,输出给编译单元。其中最主要的用法有两种:
a) 在.h文件的开头加入#ifndef, 结尾处加入#endif,这样在其他源文件中,就能够保证因为多层include的下,产生的重复加入.h问题。
b) 在源代码中加入#ifdef, #endif可以增加代码的兼容性问题。在代码目录下的编译配置文件中,会定义一些编译宏,如果在源文件中加入对编译宏的判断,那么就可以决定是否将代码输出给编译单元。
#error
当预处理器遇到#error命令时,就会打印error后的消息到屏幕上,这时,开发人员就能够得知一下编译配置上的问题。例如:
# ifndef __cplusplus
#error It is not a c++ compiler
#endif
最后,我们来看一下,为什么会存在预处理命令。因为,我们看到在C语言的语法中不存在任何机制来完成一下的功能:a)包含其他源文件 b) 宏定义 c)根据条件决定是否包含某些代码。所以,以上的工作就由预处理器来做。
5宏定义中的#和##
##是一个连接符,用于把两个参数连接在一起。#符为“字符串化”的作用,作用是将#后的参数,转变成字符串,且如果宏定义中出现#,参数不再被展开!
例如:#define STRCPY(dst, src) strcpy(dst, #src)
STRCPY(buff, abc) => strcpy(buff, "abc")
例如:#define STRCPY(a,b) strcpy(a##_p, #b)
STRCPY(Var1, abc) => strcpy(Var1_p, "abc")
6 可变参数宏...和__VA_ARGS__
__VA_ARGS__为可变参数宏,属于C99中新增的宏定义。目前只有gcc支持,vc6.0并不支持此预定义宏。主要的用法为,在宏定义是参数列表最后一个参数为省略号,也就是三个点,代表参数可变。这样可变参数宏__VA_ARGS__就可以用在之后的替换部分,__VA_ARGS__代表可变的参数。在预编译阶段,编译器会将...所代表的字符串,原封不动的替换__VA_ARGS__的位置。
7. 嵌套宏
C语言允许嵌套宏的出现,且嵌套宏的展开方法与函数是大致类似的:先展开参数,然后再分析函数,即由内向外的方式!但要注意以下两点:
1)当宏定义中出现#时,参数不再展开
2)当宏定义中出现##时, 则为先展开函数,再展开参数。