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 阅读(1179) 评论(0) 编辑 收藏 举报