c语言写预处理器,C语言预处理器详解

1.预定义符号

下面这些是C语言的预处理器定义的符号,他们都是常量十进制数,或者是常量字符串。他们用来指示调试输出来源以及为编译程序加如时间信息。而STDC用来进行条件编译。其详细意义如下:

FILE:表示进行编译的源文件名

LINE:当前行在源文件中的行数

DATE:编译时候的日期

TIME:编译时候的时间

STDC:如果编译器遵循ANSI C 那么这个值就是1,否则未定义。

2. #define

2.1 #define 替换

在程序中我们可以使用#define来进行对源程序在预处理时期的替换。

最最常见的有:

#define uint unsigned int

#define uchar unsigned char

这样可以免去敲冗长的类型名,在预处理阶段所有的 uint 和 uchar 都会被分别替换为 unsigned int 和 unsigned char 。

注意:遇到上面这样的情况,最佳的选择是使用typedef来创建别名,而不是使用宏替换。

typedef unsigned int uint;

typedef unsigned char uchar;

2.2 #define 与 函数

有时候我们采用宏来充当函数,采用#define的一个好处是,对于简短的函数段,可以直接插入在程序中,而若使用函数的话会在调用函数时产生堆栈上的开销。其次有些时候,参数类型不明确所以用函数实现不够方便。

如:

#define MAX(x,y) ( (x) > (y) ? (x) : (y))

这个定义能够求出最大值,作用类型可以使任何能够用>比较的类型。

##用来连接两个符号,举例如下:

#define ADD_NO(number) NO##number

这样 ADD_NO(1),将得到NO1,需要注意的是,你一定要确保在程序中,你定义了NO1这个变量。

#argument 被用来指代宏参数对应的字符串,下面一个例子能让你明白:

#define PRINT_INT(x) printf(“The value of” #x “is:%d\n”,x)

调用:

int a=3;

PRINT_INT(a+10);

结果为:

The value of a+10 is:3

2.3 副作用

副作用 1

观察上面的定义,每一个字符都用括号括起来了,这是因为,宏并不求值而只是简单的替换,带参数的宏定义也如此。

如果写成下面这样:

#define MAX(x,y) x > y ? x : y

在程序中假如有这样的语句:

int a=MAX(1+2,2+3);

展开后得到:

int a=1+2>2+3?1+2:2+3;

这可能不是我们想要的结果。

所以记住一点,它只是替换,并不求值。

副作用 2

在考虑上面的MAX宏,我们用下面的方式调用:

c=MAX(a++,b++);

我们希望他像函数一样,在比较完大小后,a和b的值都能加1.但是我们展开后得到:

c=((a++)>(b++)?(a++):(b++));

显然,较大的那个变量将自加两次,这显然不是我们所渴望的。所以我们一定要注意自己调用的函数是不是真的是一个函数,因为我们无法成外观上区分他们到底本质上是不是一个宏。如getchar() 它就是一个宏

副作用 3

有时候,因为宏而产生的错误是很隐蔽的,让你难以发现,考虑:

#define PRINT_TWICE(ch) putchar(ch);putchar(ch)

我们希望这个定义能够帮我们打印一个字符两次,当我们用下面这样的方法调用时:

PRINT_TWICE(fgetc(fp));

从文件中读取一个字符,然后打印两次。但是上,他从文件中读取了两次取得两个字符,然后输入到标准输出。

综上,当在使用宏的时候,一定要警惕,它是否会产生上面提到的这类不易察觉的副作用。其次为了区别宏与函数,通常用全部大写字母来为宏命名。

2.4 #undef

当我们需要在重新定义一个宏,或者要移除一个宏的时候,可以使用下面这样的形式:

#undef name

2.5 分号加不加?

当我们用宏定义了一条完整的语句的时候,我们可能希望给它后面加上一个分号,这可能不会产生大的问题。其实我们在使用了宏以后习惯性的会在其后面加上一个分号,向普通的语句一样。永远记住宏做的工作是替换,你在定义它的时候在其后加了分号,那么在调用的时候就可以不用加分号了。如过你加了那么一个分号将产生一条空语句。

虽然一条空语句可能不会影响到程序的执行,你也不会察觉,但是有时候它可能会导致发生错误,举例如下:

#define PRINT(x) putchar(x);

if(...)

PRINT(x);

else

...

这仅仅是由于if语句因为下面只有一条语句所以没有加花括号,但是这个宏实际上是两条语句。当然这个问题可以在if后加上花括号来解决。

3. 条件编译

有的时候,程序会更具编译环境来有逻辑的进行编译,举例如下:

#define DEBUG 1

#if DEBUG

打印变量的值,程序的状态等

#endif

当我们在调试程序的时候,我们可以将DEBUG设置为1,但调试完毕后将他改为0,这样我们不必去删除分布于源文件中各个地方的打印状态的语句了。

条件编译提供了以下关键字:

#if,#elif,#else,#endif,#ifdef,#ifndef

其中前四个意义明确,没错就是你理解的那样,#elif 的意思就是else if。而#ifdef name是说如果定义了name,#ifndef name 是说如果没有定义name。

还有define(…)它的意义如下:

#if defined(…) 同 #idef …

#if !define(…) 同 #ifndef

4. 文件包含

当我们的程序需要依赖于起来的头文件的时候,我们使用#include 这样的指令将源文件包含进来,就像用被包含的文件的内容替换掉#include 这句话一样。

使用尖括号是说明被包含的文件是库文件,它的路径由编译器的配置决定。使用#include "filename"这样的用双引号包围的形式,是说该文件不是库文件,它的路径引号内路径决定。如#include "cv/cv.h"是说包含当前目录下cv文件夹中的cv.h文件。

当工程很大的时候,文件互相包含,这个时候会出现同一个文件被嵌套包含多次的情况,为了避免这种情况,我们在定义头文件时,常常像下面这样写:

#ifndef __SPEACIAL_H_

#define __SPEACIAL_H_

//在这里写文件内容

#endif

每个头文件在被预处理的时候,都会定义一个特殊的宏。如果相同的头文件再次出现的时候,由于在#ifndef这里将为假,所以忽略里面的内容。

5. 其他指令

5.1 #error

#error error message

当处理到上面这条指令的时候,会出现错误信息。

5.2 #line

#line number “filename”

这个指令后面可以跟两个常量,前面一个是数字是必须有的,后面的字符串可有可无。前面的number将会修改LINE,它指明下一行的行号是number,而后面的字符串会修改FILENAME,即文件名。

5.3 #progma

这个指令因编译器不同而不同,它用来支持因编译器而异的特性。

6. 结语

预编译预指令不常用到,但是还有有必要了解,今天把处理器就介绍到这里,如有任何错误还望各位指出。

你可能感兴趣的:(c语言写预处理器)