标准C预处理器
该文章借鉴一位老兄的文章,主要是取自PROGRAMMING IN ANSI c(Third Edition).
什么是预处理器?
答:预处理器是一个iechengxu,在源代码通过编译器之前,它先对源代码进行处理。它是在成为预处理器命令行或指令的控制下操作。预处理器指令放在源程序的main函数之前。在源代码通过编译器之前,由预处理器检查所有预处理指令。如果有预处理器指令,则采取相应的动作,然后再把源程序交给编译器。
常用的预处理器指令集及其功能:
指令 作用#define 定义一个宏替换#undef 取消一个宏定义#include 指定要包含的文件#ifdef 测试某个宏已定义#endif 表示#if的结束#ifndef 测试某个宏未定义#if 测试一个编译时条件#else 当#if测试失败时,指定另一个测试
这些指令可以分为三类:(1)、宏替换指令(2)、文件包含指令(3)、编译器控制指令
以下分别介绍:一、宏替换指令宏替换是程序中的标识符被预定义的字符串(由一个或者多个标记符组成)取代的过程。预处理器在#define指令下完成这一工作。为宏定义中的表达式使用括号是明智之举。
指令最常见的有三种:1、简单宏替换#define PI 3.1415926
#define TITLE_HEIGTH 15#define EAR_MODE 0#define LOCAL_MODE 1#define STR_NOPIM “Please check the PIM card.”#define START main(){#define END }#define BLANK_LINE printf(“/n”)#define D (66 + 88)2、含参数的宏含参数的替换成为宏调用(类似于函数调用)。当调用宏时,预处理器将替换该字符串,即用实参替换形参。字符串就像一个模板。
#define CUBE(x) ((x)*(x)*(x))
#define MAX(a,b) (((a)>(b))?(a):(b))#define ABS(x) (((x)>0)?(x):(-(x)))#define at_o_msg2(x) at_o_msg(x, x_strlen(x))#define _RGB2GRAY(r,g,b) ((((r) * 3) + ((g) * 6)+ ((b) * 1)) / 10)#define swap(a,b) do { a ^= b; b ^= a; a ^= b; } while (0)3、宏嵌套一种是:在一个宏的的定义中使用另一个宏。预处理器将扩展每个#define宏,知道文本中不再有宏为止。另一种是一个宏用作另一个宏的参数。如该段最后两个例子用嵌套调用来得出x,y和z三者的最大值。
#define M 6
#define N M + 1#define SQUARE(x) ((x)*(x))#define CUBE(x) (SQUARE(x)*(x))#define SIXTH(x) (CUBE(x)*SQUARE(x))#define MAX(a,b) (((a)>(b))?(a):(b))
#define MAXEX(x,y,z) MAX(x,MAX(y,z))二、文件包含指令两种形式:#include “filename”其中,filename为含有所需宏定义或函数的文件名。此时,预处理器把filename的整个内容插到程序的源代码之中。当filename包含在双引号中时,首先从当前目录中查找该文件,然后再到标准目录中查找。
#include <filename>
在这种情况下,只在标准目录中查找该文件。
也允许被包含文件的嵌套,也就是说,一个被包含的文件又可以包含其他文件,但是,文件不能包含自身。如果没有找到被包含的文件,将报告一个错误,且编译终止。以下是一个.c文件的头文件:#include <stdio.h>
#include <string.h>#include <stdlib.h>#include “_define.h”#include “tc35605.h”#include “port.h”#include “diag.h”#include “rfboottest.h”#include “sysinfo.h”#include “bios2os.h”#include “cal.h”Main()
{
……………
}
三、编译器控制指令当开发一个大型程序时,我们可能要面临以下一种或者多种情况。(1)、已包含的一个文件中含有某些宏定义,但不知道某个宏(假设TEST)是否定义在该头文件中。而我们想确认一下TEST是否已经定义。(2)、假设某个客户有两台不同类型的计算机,要求你编写一个可用在这两个系统上运行的程序。尽管针对每个系统的某些代码会不同,但仍想使用同一个程序。(3)、如果正在开发一个程序(假设用于销售分析),在公开市场上销售。某些客户可能坚持应有某些附加特性。而我们想用一个程序来满足两种客户的需求。(4)、假设正在测试我们的系统,这是一个规模较大的系统。我们可能希望在某些地方插入printf语句,用于显示中间结果和消息,以便跟踪运行流程和错误(如果有)。这里语句称为调试语句。我们可能想让这些语句成为程序的一部分,但只有当我们需要是才起作用。
这些问题的一种解决办法是开发不同程序来不同情形的需求。另一种方法就是开发单个的全面的程序,它包含所有的可选代码,然后指定编译器跳过不需要的源代码。C 预处理器提供了一种称为条件编译的特性,它可用来关闭或打开程序的某一行或多行。
以下分别针对上面四种情况举例说明:1、情形1次情形指的是宏的条件定义。假设不管TEST宏是否已经在头文件中定义了,我们都想确保宏总是已经定义的。可以如下来实现,其中DEFINE.H为含有TEST宏定义的头文件。#define “DEFINE.H”
#ifndef TEST#define TEST 1#endif….语句 #ifndef在DEFINE.H文件中查找TEST的定义,如果没有定义,那么#ifndef与相应的#endif指令之间的代码将被执行。如果已经定义,相应的代码将被忽略。同样,不想让TEST定义,也是类似的定义。….#ifdef TEST#undef TEST#endif….例如:
#ifdef _DEBUG
#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif温馨提示:以上两种定义,不能直接定义成如下形式
#ifndef TEST // (It’s wrong!)
#undef TEST // (It’s wrong!)2、情形2此时的main函数关心的是使程序可移植。这可以如下来实现:……main(){ … #ifdef IBM_PC { //the codes are for IBM_PC … }
#else { //the codes are for HP_PC …. }
#endif ….}
如果我们想让程序在IBM_PC机上运行,可以在程序中包含如下指令: #define IBM_PC否则就不需要。注意,编译器控制指令位于函数之中,还有注意把#字符放在该行的第一列中。如果定义了IBM_PC,编译器将编译针对IBM_PC的代码;如果没有,则编译针对HP_PC的代码。以下举个实例:switch(MonDriver.OnRunFunction) { case DIALFUN:
switch(byModemRet) { case MRES_CONN: #ifdef DETTCPip PostAppMessage(_iaPPTCPIP,EV_MCONNECT,1,0); //connect OK, #endif MonDriver.RunStatus=ONLINE; MonDriver.OnRunFunction=NOMDFUN; break;
case MRES_TONE: #ifdef DETTCPIP //if no dial tone, send message to ppp PostAppMessage(_iappTCPIP,EV_MCONNECT,4,0); #endif MonDriver.RunStatus=IDLE; MonDriver.OnRunFunction=NOMDFUN; break;
case MRES_ERR: case MRES_NO: case MRES_BUSY: #ifdef DETTCPIP PostAppMessage(_iappTCPIP,EV_MCONNECT,2,0); //the line is busy, send message to ppp #endif MonDriver.RunStatus=IDLE; MonDriver.OnRunFunction=NOMDFUN; break;
default: break;
}
3、情形3这种情形类似于情形2,因此控制指令的形式如下:#ifdef ABC group-A lines
#else group-B lines#endif如果定义了ABC,则包含group-A 代码行;否则包含group-B 代码行。例如:
#ifdef PIAFS_DATA_MODEM byBuf[0] = SID_APP_PHS;
#else byBuf[0] = 0xFF;#endif4、情形4进行调试和测试是为了检测程序中的错误。编译器可以检测出语法和语义错误,但不能检测错误的算法,当程序运行时,将产生错误的结果。错误检测和隔离的过程首先是用已知的测试数据集对程序进行测试。程序分成几部分,在不同的地方放置printf语句以显示中间结果。这种语句称为调试语句。一旦把错误隔离并修正后就不再需要了。此时,我们可以把这些语句删掉,或使用如下控制指令来使它们不再为活动的。
……
#ifdef TEST { printf(“Array elements/”); for(i = 0; i < m; i ++) printf(“x[%d] = %d/n”,i, x[i]); }#endif…..#ifdef TESTprintf(….);#endif只有定义了宏TEST,才包含位于#ifdef和#endif之间的语句。一旦所有事情都搞定了,就可以删除或者取消TEST的定义。这样可以使得#ifdef TEST条件为假,因而所有测试语句都被忽略。C预处理器还支持一个更通用的测试条件形式,即#if指令,其形式如下:
#if constant_expression { statement-1; statement-2; ….. }
#endif例如 1:
#if defined(PIAFS_DATA_MODEM) && !defined(_ONPC_)UChar g_PiafsStatus; extern void pf_set_dpdial_state(unsigned char state);extern void pf_set_dp_state(unsigned char state);
#endif //PIAFS_DATA_MODEM例如 2:
#if defined YAMAHA759 || / defined YAMAHA762 || / defined YAMAHA757 || / defined OKI_2870 || / defined SUNPLUS||/ defined WINBOND Bios_SetRingVolume(0x1f); MusicPlay(1);
#endif例如 3:
#if PHONE_TEST extern _BYTE addSchedule(_BYTE *pBuf);//buf 128
#endifconstant_expression可以是以下任意形式的表达式:TEST <=3(LEVEL == 1||LEVEL == 2)MACHINE == ‘A’如果constant_expression为非零(即为真),那么位于#if 和 #endif之间的语句都被处理;否则被忽略。TEST、LEVEL等名称也可以定义为宏。
ANSI C 的其他预处理器指令#elif 提供另一种测试方法#pragma 指定某些指令#error 当发生错误时停止编译工作ANSI 标准还包括了两个新的预处理器操作:# 字符串化运算符## 标记符粘贴运算符
部分指令举例如下:
#elif指令用来构建“if…else…if”语句系列,用于测试多种条件情况。它的一般形式如下#if expression 1 statement sequence 1 #elif expression 2 statement sequence 2 …. #elif expression N statement sequence N
#endif例如:#ifdef __H300__
#define _CAL_FONT_BACK_COLOR __RGB(255,247,153)#define _CAL_PEN_COLOR __RGB(192,180,2)#elif defined(__SS71C__) || defined(__SS72C__)#define _CAL_PEN_COLOR __RGB(255,255,255)#define _CAL_FONT_BACK_COLOR __RGB(246,213,151)#endif#pragma是基于实现的指令,可以用来指定提交给编译器的不同命令。其形式如下:
#pragma name
其中,name为想要的pragma名,例如在microsoft c环境下:
#pragma loop_opt(on) 将会使循环优化后执行。如果编译器不能识别它,则被忽略。
3、#error指令#error指令用于在调试时产生诊断消息,其形式如下:#error error message当遇到#error指令时,显示错误消息并终止处理。例如:#ifndef FILE_G (或者这句这样说#if !define (FILE_G))
#error NO GRAPHICS FACILTY#endif注意:这里与#if一起使用的是一个特殊的处理器运算符defined。Defined是一个新添加的指令,带有由括号括起来的name。如果编译器不支持它,可以进行如下替换:
用 #ifndef 替换 #if !defined
用 #ifdef 替换 #if defined
#字符串化的运算符,用在宏函数的定义中。作用:该运算符允许在宏定义中使用一个形参,它将被转化为一个字符串。例如
#define sum(xy) printf(#xy“ = %f/n”, xy)
sum(a+b);
预处理器竟把下面语句行:sum(a+b)转换为printf(“a+b” “= %f /n”,a+b);
该语句又等价于 printf(“a + b = %f /n”,a + b);
ANSI标准还规定,相邻字符串将连接起来。
ANSI标准定义的标记符粘贴运算符##可以把宏定义中的两个标记符组合成一个标记符。
例如 #define combine (s1, s2) s1##s2
Combine(TOTAL,SALES)则为 TOTALSALES
(结束)
新闻热点
疑难解答