C预处理指令和C宏

     

C语言程序在被编译成可执行文件时,也许你使用的IDE只需点击一下编译按钮,或者使用gcc编译器的,也是一条命令就完成了(当然使用命令时,会有些必须的参数)。但是真正实际情况,这个过程需要经历:预处理——》编译——》链接——》装载。当然我们的编译器已经高度发达,将这些内容全部实现了。

而预处理最长被我们使用一条指令是#include。我们用它来加载头文件。同时还会有些使用预处理指令的C宏或是编译条件等。

一:首先先将C语言所有预处理指令简单介绍一下:

1.#define指令:

该条指令最常被用来定义符号常量或明显的常量,当然定一个“类函数”宏也是需要使用它的。

每个#define行由三部分组成,第一部分 是指令自身#define,第二部分是所选择的缩略语,这些缩略语称为宏(macro)。第三部分为宏替换的主体(或列表)。举例如下:

#define  PX  printf("x is %d/n",x)

#define 

PX 

printf("x is %d/n",x)

预处理指令

主体(替换列表)

其中对于第二部分(宏)有如下要求:宏的名字中不能有空格,而且必须遵循C变量命名规则:只能使用字母、数字和下划线(_)而且第一个字符不能为数字。同时要注意,对于所有预处理指令都是从#开始到第一个换行符结束。#define指令的作用范围是从出现位置开始到文件结束。

预处理器在处理时,将源文件中出现 的""用其"主体"做替换。而且这种替换是可嵌套的。这种嵌套替换举例如下:

#define  TWO  2

#define  FOUR TWO*TWO

FOUR将被做如下处理:第一次替换后变为:TWO*TWO,再次替换后变为2*2

const关键字得到C的支持后,又提供了一种创建常量的灵活方法。使用const 可以创建全局常量和局部常量、数字常量、数组常量和结构常量。对使用#define定义的符号常量可以被用来指定标准数组的大小,与const的简单举例类比。

#define  LIMIT 20

const int LIM = 50;

static int data1[LIMIT]; //合法

static int data2[LIM]; //无效

const int LIM2 = 2*LIMIT; //合法

const int LIM3 = 2 *LIM; //无效  


#define中使用参数。通过使用参数可以创建外形和作用都与函数相似的类函数宏。宏的参数也用圆括号括起来。举例如下:

#define

MEANXY

(((X+Y))/2

预处理指令

宏(XY为宏的参数)

替换主体

带参数的宏外形与函数非常相似,但是在使用时与真正的函数调用不完全相同。如果不能理解替换这种预处理形式很有可能出现意料之外的错误。见如下例子程序:

/* mac_arg.c -- macros with arguments */

#include <stdio.h>

#define SQUARE(X) X*X

#define PR(X)   printf("The result is %d./n", X)

int main(void)

{

    int x = 4;

    int z;

    printf("x = %d/n", x);

    z = SQUARE(x);

    printf("Evaluating SQUARE(x): ");

    PR(z);

    z = SQUARE(2);

    printf("Evaluating SQUARE(2): ");

    PR(z);

    printf("Evaluating SQUARE(x+2): ");

    PR(SQUARE(x+2));

    printf("Evaluating 100/SQUARE(2): ");

    PR(100/SQUARE(2));

    printf("x is %d./n", x);

    printf("Evaluating SQUARE(++x): ");

    PR(SQUARE(++x));

    printf("After incrementing, x is %x./n", x);

 

    return 0;

}

使用gcc4.3.2编译后输出的结果如下:(在不同的编译器出现的结果可能不同)

x = 4

Evaluating SQUARE(x): The result is 16.

Evaluating SQUARE(2): The result is 4.

Evaluating SQUARE(x+2): The result is 14.

Evaluating 100/SQUARE(2): The result is 100.

x is 4.

Evaluating SQUARE(++x): The result is 36.

After incrementing, x is 6.

前两项的结果正确的但是接下来的则有些出乎意外。PR(SQUARE(x+2));x值为4,那么x+2SQUARE(x+2)时应该是6*6结果应该为36。而程序运行的结果则是14.如果你使用替换原则将SQUARE(x+2)替换则变为:

x+2*x+2

这时x=4则计算结果正是14.要处理这种情况(优先级问题),可以采用对替换主题加圆括号保证优先级。最好参数本身也使用括号,保证优先级正确性。修改如下:

#define SQUARE(X)  ((X*X))

之后的情况类似。特别指出:

Evaluating SQUARE(++x): The result is 36.

将被替换成:++x*++x。这种运算顺序C语言没有给出规定,而这时对不同的编译器可能出现不同的结果,gcc4.3.2是在乘法运算前进行了x自加操作。而编译器也可以产生5*6的情况。不一而别。这里就需要注意了,在宏中一般不要使用自增或自减运算符。

2.#运算符:在宏中利用宏参数创建字符串。

如果定义如下一个宏:

#define  PSQRX) printf(“The square of X is %d/n,((X*X)))

这样使用宏:

PSQR8);

则输出为:

The  square  of  X is 64

该处双引号中的X并没有被替换,这是预处理的替换规则决定的。但是如果你希望在字符串中(双引号括起来的C语言认为是字符串)包含宏的参数。那么C语言提供了#运算符来实现。例子程序如下:

/* subst.c -- substitute in string */

#include <stdio.h>

#define PSQR(x) printf("The square of " #x " is %d./n",((x)*(x)))

int main(void)

{

    int y = 5;

    PSQR(y);

    PSQR(2 + 4);

    

    return 0;

}

输出如下:

The square of y is 25.

The square of 2 + 4 is 36.

可以看出第一次,用“y”代替了X。第二次用“2+4”代替了X

3.##运算符:预处理器的粘合剂:

 和#运算符一样,##运算符可以用于宏的替换部分。另外##还可以用于类对象宏的的替换部分。这个运算符吧两个语言符号组合成单个语言符号。例子程序如下:

// glue.c -- use the ## operator

#include <stdio.h>

#define XNAME(n) x ## n

#define PRINT_XN(n) printf("x" #n " = %d/n", x ## n);

int main(void)

{

    int XNAME(1) = 14;  // 变成int x1 = 14;

    int XNAME(2) = 20;  // 变成int x2 = 20;

    PRINT_XN(1);        // 变成printf("x1 = %d/n", x1);

    PRINT_XN(2);        // 变成printf("x2 = %d/n", x2);

    

    return 0;

}

输出如下:

x1 = 14

x2 = 20

可以看到使用##运算符后将xn合并成一个字符串。

可变参数宏:..._ _VA_ARGS_ _

具体应用举例如下:

#define  PR...) printf(_ _VA_ARGS_ _)

假设之后用下面的方式调用该宏

PR"Howdy";

PR"wetigt = %d,shipping =%f/n",wt,sp;

则第一次调用中_ _VA_ARGS_ _展开为一个参数。

"Howdy"

第二次调用中_ _VA_ARGS_ _展开为3个参数。

"wetigt = %d,shipping =%f/n",wt,sp

展开后代码为:

printf"Howdy";

printf"wetigt = %d,shipping =%f/n",wt,sp;

需要注意一点,省略号只能代替最后的宏参数。如下定义是错误的。

#define WRONG X, ... , Y) #X#_ _VA_ARGS_ _#Y

4.#include:文件包含。

预处理器发现该指令后,就会寻找后跟的文件名并把该文件的内容复制包含到当前文件中。

详细内容可参见《C语言的变量作用域及头文件》中关于头文件部分介绍。

5.#undef 取消定义:

#undef指令取消已定义的一个给定#define。举例如下:

例如;

#define LIMIT 400

....

#undef LIMIT //此处之后LIMIT是没有定义的。

但是注意,C语言提供的几个预定义宏如: _ _DATE_ __ _FILE_ _等不可被取消。

6.:#ifdef #else#endif #ifndef:条件编译指令组合

#ifdef #else#endif #ifndef通过判断之后跟随的变量是否被#define定义而形成编译的条件。#ifdef判读后面的标识符是否为定义,而#ifndef则相反,判断是否未定义。#else则与两者中其一组合使用类似,if。。。else形式。#endif用于指示结束位置。

7.#if#elif:条件编译指令组合。

#if指令更像C语言中if#if后跟常量整数表达式,如果表达式为非零值,则表达式为真。#elif则可以与#if组合形成 if 。。。else if。。。else if 的条件组合序列。(早期预处理器可能不支持#elif)。举例如下:

#if  SYS= =1

#include "test1.h"

#elif  SYS = = 2

#include "test2.h"

#elif  SYS = = 3

#include "test3.h"

#else

#include "test.h"

#endif

8.#line#error

#line指令用于重置_ _LINE_ __ _FILE_ _宏报告的行号和文件名。使用举例:

#line 1000  //把当前行重置为1000

#line 10 "cool.c"//把行号重置为10,文件名重置为cool.c

#error指令是预处理器发出一条错误消息,该消息饱和值了中的文本信息。可能的话编译过程应该中断。使用举例:

#if _ _STDC_VERSION_ _ != 199901L

#error Not C99

#endif

9.#pragma :编译器参数设定。

这个指令在现代编译器中被扩展的比较强大,同时通过必要的指令集来完成编译指示。不做详细介绍。

掌握以上预处理指令,和宏的实现。通过一些宏可以在编写程序时达到意想不到结果。

二:宏的使用技巧举例:

例一、用C宏,书写代码更简洁这段代码写网络程序的朋友都很眼熟,是Net/3mbuf的实现。

struct mbuf

{

    struct m_hdr mhdr;

    union {

        struct 

        {

            struct pkthdr MH_pkthdr; /* M_PKTHDR set */

            union 

            {

                struct m_ext MH_ext; /* M_EXT set */

                char MH_databuf[MHLEN];

            } MH_dat;

        } MH;

        char M_databuf[MLEN];        /* !M_PKTHER, !M_EXT*/

    } M_dat;

};

  上面的代码,假如我想访问最里层的MH_databuf,那么我必须写M_dat.MH.MH_dat.MH_databuf; 这是不是很长,很难写呀?这样的代码阅读起来也不明了。其实,对于MH_pkthdrMH_extMH_databuf来说,虽然不是在一个结构层次上,但是如果我们站在mbuf之外来看,它们都是mbuf的属性,完全可以压扁到一个平面上去看。所以,源码中有这么一组宏:

#define m_next      m_hdr.mh_next

#define m_len       m_hdr.mh_len

#define m_data      m_hdr.mh_data

... ...

#define m_pkthdr    M_dat.MH.MH_pkthdr

#define m_pktdat    M_dat.MH.MH_dat.MH_databuf

... ...

这样写起代码来,是不是很精练呢!

例二、用
C宏,实现跨平台和编译器的需要这方面的例子太好举了,一举一大摞,就从VC的库源码中随意copy一段出来吧。

#ifndef _CRTAPI1

#if _MSC_VER >= 800 && _M_IX86 >= 300

#define _CRTAPI1 __cdecl

#else  /* _MSC_VER >= 800 && _M_IX86 >= 300 */

#define _CRTAPI1

#endif  /* _MSC_VER >= 800 && _M_IX86 >= 300 */

#endif  /* _CRTAPI1 */

#ifndef _SIZE_T_DEFINED

typedef unsigned int size_t;

#define _SIZE_T_DEFINED

#endif  /* _SIZE_T_DEFINED */

#ifndef _MAC

#ifndef _WCHAR_T_DEFINED

typedef unsigned short wchar_t;

#define _WCHAR_T_DEFINED

#endif  /* _WCHAR_T_DEFINED */

#endif  /* _MAC */

 

#ifndef _NLSCMP_DEFINED

#define _NLSCMPERROR    2147483647  /* currently == INT_MAX */

#define _NLSCMP_DEFINED

#endif  /* _NLSCMP_DEFINED */


当然想非常熟练而且正确的使用C
语言的宏是需要时间和经验的。

:这四个步骤是在从源文件到完整可运行程序的过程。如果,你需要编译成可加载库,或者其他特殊使用,则有可能不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值