C语言中的预处理

引言

     C语言程序从源程序.c文件到可执行.elf文件需要经历的过程是:源码.c->(预处理)->预处理过的.i源文件->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->.elf可执行程序。其中预处理用预处理器、编译用编译器、汇编用汇编器、链接用链接器,再加上其它一些工具,组成编译工具链,gcc就是linux中的C语言编译工具链,这里我们仅说明预处理过程。

    预处理器处理的是预编译指令,预编译指令都是以#开始的,常用的预编译指令有:宏定义(#define)、文件包含(#include)、条件编译(#undef、#if、#else、#ifdef、#elif、#endif)、以及#error、#line和#pragma,合理使用这些指令,可使C程序易于阅读、修改、移植和调试,也利于程序模块化设计。

 本文用到的源码见 https://files.cnblogs.com/files/hfxin2001-eric-daddy/Preprocess.rar

1 宏定义指令#define

    宏定义简称“宏”,从应用角度通常分为无参宏和有参宏。预处理时对”宏”操作是:对程序中的宏名用定义的字符串进行文本替换,不做语法检查。

1.1  无参宏

    无参宏在预处理时仅是简单地将宏名用字符串替换,不做其他任何动作。

格式:#define  宏名  字符串

如:

 1 /********preprocess.c********/
 2 #include <stdio.h>
 3 #define Pi 3.1415926    //预处理时把程序中Pi全部替换为3.1415926
 4 #define STR "hello, world\n"   //预处理时把程序中STR全部替换为"hello, world\n"
 5 int main( void )
 6 {
 7     float a;
 8     a = Pi;
 9     char b[] = STR;
10     
11     printf("a= %f.\n STR b[] = %s.\n",a,STR);
12     
13     return 0;
14 }


    使用 $ gcc –E preprocess.c –o preprocess.i  将preprocess.c 预处理为preprocess.i,查看preprocess.i的内容(见图),可见无参宏仅是将宏名用字符串替换,不做其他任何动作,且即使” ”中的字符串包含宏名,也不会进行宏替换,如printf中的STR。

关于无参宏的说明:

(1)预处理是在编译之前的处理动作,不做语法检查,语法检查是编译器的主要工作;

(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义;

(3)宏定义通常写在文件的最开头,作用域为其后的程序;

(4)宏名一般用大写;

(5)宏定义末尾不加分号;

(6)可以用#undef命令终止宏定义的作用域,如;

#define  MAX(a, b)  ((a)>(b)?(a):(b))
#undef MAX(a, b) //#undef 就取消了MAX(a, b)。

(7)宏定义允许嵌套;

(8)字符串( " " )中永远不包含宏,亦即使” ”中的字符串包含宏名,也不会进行宏替换;

(9)宏定义不分配内存,变量定义分配内存;

(10)宏定义不存在类型问题,它的参数也是无类型的。

 

1.2  有参宏

    宏定义可以带参数,类似函数,如:

#define  MAX(a, b)  ((a)>(b)?(a):(b))

再如:

/********preprocess.c********/
#include <stdio.h>
#define  MAX(a, b)  ((a)>(b)?(a):(b))
#define Pi 3.1415926 
int main( void )
{
float x,y,z;
x = Pi;
y = 5.0;
z = MAX(x,y);

printf(" x= %f.\n y= %f.\n c= %f.\n ",x,y,z);

return 0;
}

使用 $ gcc –E preprocess.c –o preprocess.i  将preprocess.c 预处理为preprocess.i,查看preprocess.i的内容(见图),可见有参宏不但将宏名用字符串替换,同时进行了参数传递。

输出结果为

关于有参宏的说明:

(1)宏定义只占用编译时间、不占用运行时间,不分配内存;函数调用占用运行时间(需要分配内存、保留现场、值传递和返回值等开销)。

(2)宏中的参数没有类型,也不做类型检查,只做文本替换,所以传参数时要格外小心。

(3)宏定义时最好每个参数都加括号,最外层也要加括号,避免出现意想不到的错误。

#define S(r) r*r
area=S(a+b);//第一步换为area=r*r;,第二步被换为area=a+b*a+b;,出现错误

正确的宏定义为

#define S(r) ((r)*(r))

1.3 关键字inline

    函数定义时如使用inline关键字,编译器会在编译时,在函数调用处将函数代码展开(类似宏替换),目的是减少因函数调用开销,弊端是编译后的文件会变大,如:   

static inline int rwsem_is_locked(struct rw_semaphore *sem) 
{ return sem->count != 0; }

2 头文件包含指令#include

    #include的作用是将头文件包含到当前程序中,如

#include <stdio.h
#include “math.h”

    将共用模块制作为头文件,然后通过#inlcude将头文件包含到程序中,实现共用模块的重复利用,避免重复编写代码,对模块化程序设计带来很大好处,例如函数库的制作和使用。#include包含头文件的处理过程是,程序进行预处理时,预处理器将头文件的内容原封不动地复制到#include 处,然后再进行编译。

#include <stdio.h>和#include “math.h”的区别在于头文件的检索路径的差异,具体为

(1)#include “math.h”,首先在当前工作目录检索头文件;如果未找到,则在命令行指明的目录中检索;如果仍未找到,则在包含目录(即系统或编译器的环境变量所指定的目录)中检索;如仍未找到,则报错。但通常都用于包含当前工作目录下的头文件。

(2) #include <stdio.h>,首先在命令行指明的目录中检索,如果未找到,则在包含目录(即系统或编译器的环境变量所指定的目录)中检索;如仍未找到,则报错,不会在当前工作目录中检索。但通常都用于包含包含目录(即系统或编译器的环境变量所指定的目录)下的头文件。

头文件写法的说明:

(1).c文件中写变量、函数的定义;

(2).h文件中写变量、函数的声明;

(3) 如果有数据类型的定义和宏定义,写在头文件(.h)中;

(4) 头文件一定使用#ifndef...#define....#endif指令,防止头文件被重包含的语句;

(5) .c文件中需要包含自己的.h文件

    使用 $ gcc –E preprocess.c –o preprocess.i  将preprocess.c 预处理为preprocess.i,查看preprocess.i的内容,可见stdio.h的内容已经原封不动地copy到程序中啦。

 

3 条件编译指令#if、#elif、#else、#ifdef、#ifudef、 #endif、#undef

3.1 #if、#elif、#else、#ifdef、

    通过条件编译指令可以有选择地编译程序源代码,商业软件公司广泛应用条件编译来提供和维护摸个程序的多个版本,如windows的家庭版、专业版。

       #if  表达式

            语句段1

#else

            语句段2

 #endif

 

 

作用:若表达式为真,执行语句段1,否则执行语句段2  

         #if   表达式1

                语句段1

          #elif 表达式2

                 语句段2

#else

                语句段3

#endif

作用:若表达式1真,执行语句段1,否则判断表达式2,若表达式2为真,则执行语句段2,否则执行语句段3

 

 3.1.1示例1

 

 1 /*********preprocess.c*********/
 2 #include <stdio.h>
 3 #define DEBUG    
 4 int main()
 5 {
 6    #ifdef DEBUG
 7   printf("Debugging\n");
 8    #else
 9   printf("Not debugging\n");
10    #endif
11    printf("Running\n");
12    return 0;
13 }

    使用 $ gcc –E preprocess.c –o preprocess.i  将preprocess.c 预处理为preprocess.i,查看preprocess.i的内容见图,可见宏不但将宏名用字符串替换,无用行还用空白进行了替换。

输出结果

3.1.2 示例2

 

 1 /*********preprocess.c*********/
 2 #include <stdio.h>
 3 #define TWO
 4 int main()
 5 {
 6    #ifdef ONE
 7   printf("1\n");
 8    #elif defined TWO
 9   printf("2\n");
10    #else
11   printf("3\n");
12    #endif
13 }

    使用 $ gcc –E preprocess.c –o preprocess.i  将preprocess.c 预处理为preprocess.i,查看preprocess.i的内容见图,可见宏不但将宏名用字符串替换,无用行还用空白进行了替换。

输出结果

 

3.2 #ifdef和#ifndef

#ifdef和#ifndef主要用于防止头文件重复包含,通常放置在.h头文件最前面,语法格式如:

       #ifdef 宏名

                  语句段

#endif

作用:若此前已定义了宏名,则执行语句段。

         #ifndef  宏名

                      语句段

#endif

作用:若此前没有定义宏名,则执行语句段。

#else可以用于#ifdef和#ifndef中,但#elif不可以。

3.2.1 示例

#ifndef FUNCA_H

#define FUNCA_H

//头文件内容

#endif

    这样就可以防止头文件FUNCA.h被重复包含。假设a.h包含了FUNCA.h,b.h包含了a.h和FUNCA.h,那么FUNCA.h就被重复包含了,编译时会报type redefination之类的错误。

 

4 #error

    #error是C/C++的预处理命令,当预处理器预处理到#error命令时,将停止编译并输出用户自定义的错误消息。

    语法:#error  [用户自定义的错误消息]

4.1 示例1

 1 /*
 2 *检查编译此源文件的编译器是不是C++编译器
 3 *如果使用的是C语言编译器,则执行#error命令
 4 *如果使用的是 C++ 编译器,则跳过#error命令
 5 */
 6 #include <stdio.h>
 7 #define __cplusplus   0
 8 #ifndef __cplusplus
 9 #error Darling ,This is not C++ compiler! 
10 #endif
11 
12 int main()
13 {
14 printf("Hello,World!\n");
15 return 0;
16 }

输出结果


4.2 示例2

 1 #include <stdio.h>
 2 #define __cplusplus   0
 3 #undef __cplusplus  // 取消__cplusplus宏定义
 4 #ifndef __cplusplus
 5 #error Darling ,This is not C++ compiler! 
 6 #endif
 7 
 8 int main()
 9 {
10 printf("Hello,World!\n");
11 return 0;
12 }

输出结果

5 #line

《后续待补》

6 #pragma

《后续待补》

 

posted on 2016-08-30 13:54  Eric_daddy  阅读(1174)  评论(0编辑  收藏  举报

导航