Chinaunix首页 | 论坛 | 博客
  • 博客访问: 669742
  • 博文数量: 134
  • 博客积分: 3158
  • 博客等级: 中校
  • 技术积分: 1617
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-30 22:36
文章分类

全部博文(134)

文章存档

2012年(2)

2011年(28)

2010年(68)

2009年(35)

2008年(1)

我的朋友

分类: C/C++

2009-05-18 18:54:41

标准C预处理器(thanklife

该文章借鉴一位老兄的文章,主要是取自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宏,知道文本中不再有宏为止。另一种是一个宏用作另一个宏的参数。如该段最后两个例子用嵌套调用来得出xyz三者的最大值。

#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 

在这种情况下,只在标准目录中查找该文件。

也允许被包含文件的嵌套,也就是说,一个被包含的文件又可以包含其他文件,
但是,文件不能包含自身。如果没有找到被包含的文件,将报告一个错误,且编译终止。
以下是一个.c文件的头文件:
#include
#include
#include
#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
....
语句 #ifndefDEFINE.H文件中查找TEST的定义,如果没有定义,那么#ifndef与相应的
#endif
指令之间的代码将被执行。如果已经定义,相应的代码将被忽略。
同样,不想让TEST定义,也是类似的定义。
....
#ifdef TEST
#undef TEST
#endif
....

例如:

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static 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;
#endif

4、情形4
进行调试和测试是为了检测程序中的错误。编译器可以检测出语法和语义错误,但不能
检测错误的算法,当程序运行时,将产生错误的结果。
错误检测和隔离的过程首先是用已知的测试数据集对程序进行测试。程序分成几部分,在
不同的地方放置printf语句以显示中间结果。这种语句称为调试语句。一旦把错误隔离并
修正后就不再需要了。此时,我们可以把这些语句删掉,或使用如下控制指令来使它们
不再为活动的。

  ......
#ifdef  TEST
  {
   printf("Array elements\");
   for(i = 0; i < m; i ++)
     printf("x[%d] = %d\n",i, x[i]);
 }
#endif
.....
#ifdef TEST
printf(....);
#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
#endif

constant_expression可以是以下任意形式的表达式:
TEST <=3
(LEVEL == 1||LEVEL == 2)
MACHINE == 'A'
如果constant_expression为非零(即为真),那么位于#if #endif之间的语句都被处理;
否则被忽略。TESTLEVEL等名称也可以定义为宏。


ANSI C 的其他预处理器指令
#elif   
提供另一种测试方法
#pragma 
指定某些指令
#error  
当发生错误时停止编译工作
ANSI
标准还包括了两个新的预处理器操作:
#   
字符串化运算符
##  
标记符粘贴运算符

部分指令举例如下:

1#elif指令

#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

2#pragma指令

#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一起使用的是一个特殊的处理器运算符definedDefined是一个新添加的指令,带有由括号括起来的name。如果编译器不支持它,可以进行如下替换:

#ifndef 替换 #if !defined

#ifdef 替换 #if defined

4#字符串化运算符

#字符串化的运算符,用在宏函数的定义中。作用:该运算符允许在宏定义中使用一个形参,它将被转化为一个字符串。例如

#define sumxy printf#xy = %f\n” xy

sum(a+b);

预处理器竟把下面语句行:suma+b)转换为printf(“a+b” “= %f \n”,a+b);

该语句又等价于 printf(“a + b = %f \n”,a + b);

ANSI标准还规定,相邻字符串将连接起来。

5##标记符粘贴运算符

ANSI标准定义的标记符粘贴运算符##可以把宏定义中的两个标记符组合成一个标记符。

例如 #define combine (s1, s2) s1##s2

Combine(TOTAL,SALES)则为 TOTALSALES

(结束)

 

阅读(1628) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~