• 预处理器之我见 - [C++]

    2008-04-23

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://egeho123.blogbus.com/logs/19589370.html

    预处理器之我见

     预处理器指令主要有以下几种:
    #define   定义一个宏替换
    #undef    取消一个宏定义
    #include   指定要包含的文件
    #ifdef    测试某个宏已定义
    #endif    表示#if的结束
    #ifndef   测试某个宏未定义
    #if       测试一个编译时条件
    #else     当#if测试失败时,指定另一个测试

    这些指令可以分为三类:
    (1)、宏替换指令
    (2)、文件包含指令
    (3)、编译器控制指令

    以下分别介绍:
    一、宏替换指令
    宏替换指令最常见的有三种:
    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 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包含在双引号中时,首先从当前
    目录中查找该文件,然后再到标准目录中查找。
    #define  "filename"
    在这种情况下,只在标准目录中查找该文件。
    也允许被包含文件的嵌套,也就是说,一个被包含的文件又可以包含其他文件,
    但是,文件不能包含自身。
    如果没有找到被包含的文件,将报告一个错误,且编译终止。
    以下是一个.c文件的头文件:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include "_define.h"
    #include "tc35605.h"
    #include "port.h"
    #include "bios2os.h"
    #include "flash.h"
    #include "lcddrv.h"
    #include "uart.h"
    #include "_vkey.h"
    #include "bios.h"
    #include "flashapi.h"
    #include "diag.h"
    #include "rfboottest.h"
    #include "sysinfo.h"
    #include "bios2os.h"
    #include "cal.h"

    三、编译器控制指令
    当开发一个大型程序时,我们可能要面临以下一种或者多种情况。
    (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_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之间的语句都被处理;
    否则被忽略。TEST、LEVEL等名称也可以定义为宏。


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

    部分指令举例如下:
    (a)、#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

    (b)、#error指令
    #error指令用于在调试时产生诊断消息,其形式如下:
    #error error message
    当遇到#error指令时,显示错误消息并终止处理。例如:
    #ifndef FILE_G (或者这句这样说#if !define (FILE_G))
    #error NO GRAPHICS FACILTY
    #endif

    (结束)


    历史上的今天:

    ACE配置文件 2008-04-23

    收藏到:Del.icio.us