C/C++预编译指令总结

C/C++中宏总结C程序的源代码中可包括各种编译指令,这些指令称为预处理命令或预处理器。虽然它们实际上不是C语言的一部分,但却扩展了C程 序设计的环境。
预处理指令的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,宏替换,条件编译和布局控制4种。
文件包含:#include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。
宏替换:#define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。
条件编译:#if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。
布局控制:#pragma,这也是我们应用预处理的一个重要方面,主要功能是设定编译器的状态,者是指示编译器完成一些特定的动作或为编译程序提供非常规的控制流信息。
其他:#error, #line等。

一。预处理指令的格式:
预处理指令的格式如下:
#directive tokens
#define identifier token-stringop
#define identifier[( identifieropt, ... , identifieropt )] token-stringopt

#符号应该是这一行的第一个非空字符,一般我们把它放在起始位置。如果指令一行放不下,可以通过“\”进行控制,例如:
#define Error if(error) exit(1)
等价于
#define Error \
if(error) exit(1)

二。文件包含指令:
这种预处理使用方式是最为常见的,平时我们编写程序都会用到,最常见的用法是:
#include <iostream> //标准库头文件
#include <iostream.h> //旧式的标准库头文件
#include "IO.h" //用户自定义的头文件
#include "../file.h" //UNIX下的父目录下的头文件
#include "/usr/local/file.h" //UNIX下的完整路径
#include "..\file.h" //Dos下的父目录下的头文件
#include "\usr\local\file.h" //Dos下的完整路径

注意两个小问题:
1、我们用<iostream>还是<iostream.h>?
我们主张使用<iostream>,而不是<iostream.h>,为什么呢?我想你可能还记得我曾经给出过几点理由,这里我大致的说一下:
首先,.h格式的头文件早在989月份就被标准委员会抛弃了,我们应该紧跟标准,以适合时代的发展。其次,iostream.h只支持窄字符集,iostream则支持窄/宽字符集。还有,标准对iostream作了很多的改动,接口和实现都有了变化。最后,iostream组件全部放入namespace std中,防止了名字污染。
2、<io.h>和"io.h"的区别?
其实他们唯一的区别就是搜索路径不同:对于#include <io.h> ,编译器从标准库路径开始搜索对于#include "io.h",编译器从用户的工作路径开始搜索。

三。宏替换:
       1.定义宏和取消宏指令:
//定义宏
#define [MacroName] [MacroValue]
//取消宏
#undef [MacroName]
2.无参数宏:
//普通宏
#define PI (3.1415926)
3.带参数的宏:
带参数的宏
#define max(a,b) ((a)>(b)? (a),(b))
关键是十分容易产生错误,包括机器和人理解上的差异等等。
4.单行宏与多行宏:
define可以替代多行的代码,关键是要在每一个换行的时候加上一个"/" ,最后一行不加。例如MFC里的宏定义:
#define MACRO(arg1, arg2) do { /
/* declarations */ /
stmt1; /
stmt2; /
/* ... */ /
} while(0) /* (no trailing ; ) */

5.宏定义中的连接符:
    (1)连接符##:它将两个标记符(token)链接为一个,并注意它可以在第二个标记符的前面或后面。例如:
#define A(x)  T_##xint A(1) = 10; //等效于int T_1 = 10;
#define A(x)  Tx##__int A(1) = 10; //等效于int T1__ = 10;2)连接符#:它将标记符变换为字符串,即加双引号。例如:
#define C(x) #x
 则C(1+1) 即 ”1+1”.

    (3)连接符#@:它将单字符标记符变换为单字符,即加单引号。例如:
#define B(x) #@x
 则B(a)即’a’,B(1)即’1’.但B(abc)却不甚有效.

6.变参宏:......在C宏中称为Variadic Macro,也就是变参宏。比如:
   #define myprintf(templt, ......) fprintf(stderr,templt,__VA_ARGS__)
   // 或者
   #define myprintf(templt, args......) fprintf(stderr,templt,args)
四。条件编译:
        条件编译多用于版本控制和防止头文件重复包含等方面,主要有三套规则:
        1.#if 语句:
          #if expressin1
          your code1
          #elif expression2 //呵呵,elif, 意指else if
         your code2
         #else
         your code3
          #enif
        2.#if defined语句:
           #if defined(identifier1)||defined(identifier2)||...
           your code
           #elif defined(identifier3)||defined(identifier4)||...
           your code
           #else
           your code
           #endif
          3.#ifdef或#ifndef语句:
           #ifdef identifier (#ifndef identifier)
           your code
           #elif expression  // elif again!!!
           your code
           #else
           your code
           #endif
五。布局控制:
        #pragma 语法比较复杂,而且只对不同的编译器有不同的实现。因此不在此论述,将在另一篇文章中详述。
六。其他:
        1.#error:
#error语法如下:
#error info
例如:
#ifndef UNIX
#error This software requires the UNIX OS.
#endif
       2.#line:
#line的语法如下:
#line number filename
例如:
#line 30 a.h
其中,文件名a.h可以省略不写。这条指令可以改变当前的行号和文件名,例如上面的这条预处理指令就可以改变当前的行号为30,文件名是a.h。初看起来似乎没有什么用,不过,他还是有点用的,那就是用在编译器的编写中,我们知道编译器对C++源码编译过程中会产生一些中间文件,通过这条指令,可以保证文件名是固定的,不会被这些中间文件代替,有利于进行分析。

七。常用的预编译指令的实例:

1、防止一个头文件被重复包含
#ifndef COMDEF_H
#define COMDEF_H
 //头文件内容
#endif
2、重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。
typedef  unsigned char      boolean;     /* Boolean value type. */
typedef  unsigned long int  uint32;      /* Unsigned 32 bit value */
typedef  unsigned short     uint16;      /* Unsigned 16 bit value */
typedef  unsigned char      uint8;       /* Unsigned 8  bit value */
typedef  signed long int    int32;       /* Signed 32 bit value */
typedef  signed short       int16;       /* Signed 16 bit value */
typedef  signed char        int8;        /* Signed 8  bit value */
//下面的不建议使用
typedef  unsigned char     byte;         /* Unsigned 8  bit value type. */
typedef  unsigned short    word;         /* Unsinged 16 bit value type. */
typedef  unsigned long     dword;        /* Unsigned 32 bit value type. */
typedef  unsigned char     uint1;        /* Unsigned 8  bit value type. */
typedef  unsigned short    uint2;        /* Unsigned 16 bit value type. */
typedef  unsigned long     uint4;        /* Unsigned 32 bit value type. */
typedef  signed char       int1;         /* Signed 8  bit value type. */
typedef  signed short      int2;         /* Signed 16 bit value type. */
typedef  long int          int4;         /* Signed 32 bit value type. */
typedef  signed long       sint31;       /* Signed 32 bit value */
typedef  signed short      sint15;       /* Signed 16 bit value */
typedef  signed char       sint7;        /* Signed 8  bit value */
3、得到指定地址上的一个字节或字
#define  MEM_B( x )  ( *( (byte *) (x) ) )
#define  MEM_W( x )  ( *( (word *) (x) ) )
4、求最大值和最小值
   #define  MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )
   #define  MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )
5、得到一个field在结构体(struct)中的偏移量
#define FPOS( type, field ) /
/*lint -e545 */ ( (dword) &(( type *) 0)-> field ) /*lint +e545 */
6、得到一个结构体中field所占用的字节数
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
7、按照LSB格式把两个字节转化为一个Word
#define  FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )
8、按照LSB格式把一个Word转化为两个字节
#define  FLOPW( ray, val ) /
  (ray)[0] = ((val) / 256); /
  (ray)[1] = ((val) & 0xFF)
9、得到一个变量的地址(word宽度)
#define  B_PTR( var )  ( (byte *) (void *) &(var) )
#define  W_PTR( var )  ( (word *) (void *) &(var) )
10、得到一个字的高位和低位字节
#define  WORD_LO(xxx)  ((byte) ((word)(xxx) & 255))
#define  WORD_HI(xxx)  ((byte) ((word)(xxx) >> 8))
11、返回一个比X大的最接近的8的倍数
#define RND8( x )       ((((x) + 7) / 8 ) * 8 )
12、将一个字母转换为大写
#define  UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) )
13、判断字符是不是10进值的数字
#define  DECCHK( c ) ((c) >= '0' && (c) <= '9')
14、判断字符是不是16进值的数字
#define  HEXCHK( c ) ( ((c) >= '0' && (c) <= '9') ||/
                       ((c) >= 'A' && (c) <= 'F') ||/
((c) >= 'a' && (c) <= 'f') )
15、防止溢出的一个方法
#define  INC_SAT( val )  (val = ((val)+1 > (val)) ? (val)+1 : (val))
16、返回数组元素的个数
#define  ARR_SIZE( a )  ( sizeof( (a) ) / sizeof( (a[0]) ) )
17、返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#define MOD_BY_POWER_OF_TWO( val, mod_by ) /
           ( (dword)(val) & (dword)((mod_by)-1) )
18、对于IO空间映射在存储空间的结构,输入输出处理
  #define inp(port)         (*((volatile byte *) (port)))
  #define inpw(port)        (*((volatile word *) (port)))
  #define inpdw(port)       (*((volatile dword *)(port)))
  #define outp(port, val)   (*((volatile byte *) (port)) = ((byte) (val)))
  #define outpw(port, val)  (*((volatile word *) (port)) = ((word) (val)))
  #define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val)))
19、使用一些宏跟踪调试
ANSI标准说明了五个预定义的宏名。它们是:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
C++中还定义了 __cplusplus
如果编译器不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。
__LINE____FILE__ 宏指示,#line指令可以改变它的值,简单的讲,编译时,它们包含程序的当前行数和文件名。
__DATE__ 宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期, VC中可以使用。
__TIME__ 宏指令包含程序编译的时间。时间用字符串表示,其形式为:分:秒,VC中可以使用。
__STDC__ 宏指令的意义是编译时定义的。一般来讲,如果__STDC__已经定义,编译器将仅接受不包含任何非标准扩展的标准C/C++代码。如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。
__cplusplus 与标准c++一致的编译器把它定义为一个包含至少6为的数值。与标准c++不一致的编译器将使用具有5位或更少的数值。
可以定义宏,例如:
当定义了_DEBUG,输出数据信息和所在文件所在行
#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
#else
#define DEBUGMSG(msg,date)
#endif

20、宏定义防止错误使用小括号包含。
例如:
有问题的定义:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;}
应该使用的定义: #difne DO(a,b) do{a+b;a++;}while(0)
例如:
if(addr)
    DUMP_WRITE(addr,nr);
else
    do_somethong_else();
宏展开以后变成这样:
if(addr)
    {memcpy(bufp,addr,nr); bufp += nr;};
else
    do_something_else();
gcc在碰到else前面的“;”时就认为if语句已经结束,因而后面的else不在if语句中。而采用do{} while(0)的定义,在任何情况下都没有问题。而改为 #difne DO(a,b) do{a+b;a++;}while(0) 的定义则在任何情况下都不会出错

21. 宏定义中连接符与宏参数展开:
定义带宏参数的的宏时经常会用到连接符##、#及#@。如果宏参数本身也是一个宏的话,需要注意的是凡宏定义里有用"#"或"##"的地方宏参数是不会再展开。
举个例子如下:
#define A          (2)
       #define STR(s)     #s
       #define CONS(a,b)  int(a##e##b)
   printf("int max: %s\n",  STR(INT_MAX));    // INT_MAX #include<climits>
   这行会被展开为:
   printf("int max: %s\n", "INT_MAX");
   printf("%s\n", CONS(A,A));               // compile error
   这一行则被展开为:
   printf("%s\n", int(AeA));
   INT_MAXA都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
   加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数
   #define A           (2)
   #define _STR(s)     #s
   #define STR(s)      _STR(s)          // 转换宏,展开宏参数s,如果s是另一个宏的话
   #define _CONS(a,b)  int(a##e##b)
   #define CONS(a,b)   _CONS(a,b)       // 转换宏,展开宏参数s,如果s是另一个宏的话
   printf("int max: %s\n", STR(INT_MAX));          // INT_MAX,int型的最大值
   输出为: int max: 0x7fffffff
   STR(INT_MAX) -->  _STR(0x7fffffff) 然后再转换成字符串;
   printf("%d\n", CONS(A, A));
   输出为:200
   CONS(A, A)  -->  _CONS((2), (2))  --> int((2)e(2))

   22。宏中的变参:
        ......在C宏中称为Variadic Macro,也就是变参宏。比如:
   #define myprintf(templt, ......) fprintf(stderr,templt,__VA_ARGS__)
   // 或者
   #define myprintf(templt, args......) fprintf(stderr,templt,args)
   第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最后一项出现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:myprintf(templt,);的形式。这时的替换过程为:
   myprintf("Error!\n",);
   替换为:
   fprintf(stderr,"Error!\n",);
   这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:
   myprintf(templt);
   而它将会被通过替换变成:
   fprintf(stderr,"Error!\n",);
   很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:
   #define myprintf(templt,......) fprintf(stderr,templt, ##__VAR_ARGS__)
   这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:
   myprintf(templt);
   被转化为:
   fprintf(stderr,templt);
   这样如果templt合法,将不会产生编译错误。
   另外,在vxworks中,还可以允许下面的宏定义:
   #define myprintf(arg...)        printf(arg)
   宏的第一个参数就设置为变参,因此下面的几种使用方式都是正确的:
   myprintf("number");
   myprintf("number %d",2);
   myprintf("number %d %d",2,3);

转自:http://blog.csdn.net/kevinzhangyang/article/details/6678396

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值