C语言中的预处理详解

目录

一.预处理的工作方式… 3
1.1.预处理的功能… 3
1.2预处理的工作方式… 3
二.预处理指令… 4
2.1.预处理指令… 4
2.2.指令规则… 4
三.宏定义命令—-#define. 4
3.1.无参数的宏… 4
3.2带参数的宏… 5
3.3.预处理操作符#和##. 6
3.3.1.操作符#. 6
3.3.2.操作符##. 6
四.文件包含——include. 6
五.条件编译… 7
5.1使用#if 7
5.2使用#ifdef和#ifndef 9
5.3使用#defined和#undef 10
六.其他预处理命令… 11
6.1.预定义的宏名… 11
6.2.重置行号和文件名命令————#line. 11
6.3.修改编译器设置命令 ————#pragma. 12
6.4.产生错误信息命令 ————#error 12
七.内联函数… 13

在嵌入式系统编程中不管是内核的驱动程序还是应用程序的编写,涉及到大量的预处理与条件编译,这样做的好处主要体现在代码的移植性强以及代码的修改方便等方面。因此引入了预处理与条件编译的概念。
在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。
一.预处理的工作方式

1.1.预处理的功能

在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:

●将源文件中以”include”格式包含的文件复制到编译的源文件中。
●用实际值替换用“#define”定义的字符串。
●根据“#if”后面的条件决定需要编译的代码。

1.2预处理的工作方式

预处理的行为是由指令控制的。这些指令是由#字符开头的一些命令。

define指令定义了一个宏—用来代表其他东西的一个命令,通常是某一个类型的常量。预处理会通过将宏的名字和它的定义存储在一起来响应#define指令。当这个宏在后面的程序中使用到时,预处理器”扩展”了宏,将宏替换为它所定义的值。

include指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分“包含”进来。例如:下面这行命令:

include

define 宏名 字符串

在以上宏定义语句中,各部分的含义如下:
● #:表示这是一条预处理命令(凡是以“#”开始的均为预处理命令)。
●define:关键字“define”为宏定义命令。
●宏名:是一个标示符,必须符合C语言标示符的规定,一般以大写字母标示宏名。
●字符串:可以是常数,表达式,格式串等。在前面使用的符号常量的定义就是一个无参数宏定义。
Notice:
预处理命令语句后面一般不会添加分号,如果在#define最后有分号,在宏替换时分号也将替换到源代码中去。在宏名和字符串之间可以有任意个空格。
Eg:#define PI 3.14
在使用宏定义时,还需要注意以下几点:
●宏定义是宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。
●宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。
●宏名在源程序只能够若用引号括起来,则预处理程序不对其作宏替换。
●宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层替换。
●习惯上宏名可用大写字母表示,以方便与变量区别。但也允许用小写字母。

3.2带参数的宏

define命令定义宏时,还可以为宏设置参数。与函数中的参数类似,在宏定于中的参数为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,还要用实参去代换形参。

带参宏定义的一般形式为:

define 宏名(形参表) 字符串

在定义带参数的宏时,宏名和形参表之间不能有空格出现,否则,就将宏定义成为无参数形式,而导致程序出错。
Eg:#define ABS(x) (x)<0?-(x):(x)
以上的宏定义中,如果x的值小于0,则使用一元运算符(-)对其取负,得到正数。
带参的宏和带参的函数相似,但其本质是不同的。使用带参宏时,在预处理时将程序源代码替换到相应的位置,编译时得到完整的目标代码,而不进行函数调用,因此程序执行效率要高些。而函数调用只需要编译一次函数,代码量较少,一般情况下,对于简单的功能,可使用宏替换的形式来使用。

3.3.预处理操作符#和##

3.3.1.操作符#

在使用#define定义宏时,可使用操作符#在字符串中输出实参。Eg:

define AREA(x,y) printf(“长为“#x”,宽为“#y”的长方形的面积:%d\n”,(x)*(y));

3.3.2.操作符##

与操作符#类似,操作符##也可用在带参宏中替换部分内容。该操作符将宏中的两个部分连接成一个内容。例如,定义如下宏:

define VAR(n) v##n

当使用一下方式引用宏:
VAR(1)
预处理时,将得到以下形式:
V1
如果使用以下宏定义:

define FUNC(n) oper##n

当实参为1时,预处理后得到一下形式:
oper1

四.文件包含——include

 当一个C语言程序由多个文件模块组成时,主模块中一般包含main函数和一些当前程序专用的函数。程序从main函数开始执行,在执行过程中,可调用当前文件中的函数,也可调用其他文件模块中的函数。
如果在模块中要调用其他文件模块中的函数,首先必须在主模块中声明该函数原型。一般都是采用文件包含的方法,包含其他文件模块的头文件。

文件包含中指定的文件名即可以用引号括起来,也可以用尖括号括起来,格式如下:

include< 文件名>

include“文件名”

如果使用尖括号<>括起文件名,则编译程序将到C语言开发环境中设置好的 include文件中去找指定的文件。
因为C语言的标准头文件都存放在include文件夹中,所以一般对标准头文件采用尖括号;对编程自己编写的文件,则使用双引号。如果自己编写的文件不是存放在当前工作文件夹,可以在#include命令后面加在路径。
#include命令的作用是把指定的文件模块内容插入到#include所在的位置,当程序编译链接时,系统会把所有#include指定的文件链接生成可执行代码。文件包含必须以#开头,表示这是编译预处理命令,行尾不能用分号结束。
#include所包含的文件,其扩展名可以是“.c”,表示包含普通C语言源程序。也可以是 “.h”,表示C语言程序的头文件。C语言系统中大量的定义与声明是以头文件形式提供的。
通过#define包含进来的文件模块中还可以再包含其他文件,这种用法称为嵌套包含。嵌套的层数与具体C语言系统有关,但是一般可以嵌套8层以上。

五.条件编译

预处理器还提供了条件编译功能。在预处理时,按照不同的条件去编译程序的不同部分,从而得到不同的目标代码。使用条件编译,可方便地处理程序的调试版本和正式版本,也可使用条件编译使程序的移植更方便。
5.1使用#if
与C语言的条件分支语句类似,在预处理时,也可以使用分支,根据不同的情况编译不同的源代码段。

if 的使用格式如下:

if 常量表达式

程序段

else

程序段

endif

该条件编译命令的执行过程为:若常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。
Eg:

define DEBUG 1

int main()
{
int i,j;
char ch[26];

for(i=’a’;j=0;i<=’z’;i++,j++)
{
ch[j]=i;
#if DEBUG
printf(“ch[%d]=%c\n”,j,ch[j]);
#endif
}
for(j=0;j<26;j++)
{
printf(“%c”,ch[j]);
}
return 0;
}

if预编译命令还可使用多分支语句格式,具体格式如下:

if 常量表达式 1

程序段 1

elif 常量表达式 2

程序段 2

… …

elif 常量表达式 n

程序段 n

else

程序段 m

endif

关键字#elif与多分支if语句中的else if类似。
Eg:

define os win

if os=win

#include"win.h"

elif os=linux

#include"linux.h"

elif os=mac

#include"mac.h"

endif

if和#elif还可以进行嵌套,C89标准中,嵌套深度可以到达8层,而C99允许嵌套达到63层。在嵌套时,每个#endif,#else或#elif与最近的#if或#elif配对。

Eg:

define MAX 100

define OLD -1

int main()
{
int i;

if MAX>50

{
#if OLD>3
{
i=1;
{
#elif OLD>0
{
i=2;
}
#else
{
i=3;
}
#endif
}

else

{
#if OLD>3
{
i=4;
}
#elif OLD>4
{
i=5;
}
#else
{
i=6;
}
#endif
}

endif

return 0;
}

5.2使用#ifdef和#ifndef

在上面的#if条件编译命令中,需要判断符号常量定义的具体值。在很多情况下,其实不需要判断符号常量的值,只需要判断是否定义了该符号常量。这时,可不使用#if命令,而使用另外一个预编译命令———#ifdef.

ifdef命令的使用格式如下:

ifdef 标识符

程序段 1

else

程序段 2

endif

其意义是,如果#ifdef后面的标识符已被定义过,则对“程序段1”进行编译;如果没有定义标识符,则编译“程序段2”。一般不使用#else及后面的“程序2”。
而#ifndef的意义与#ifdef相反,其格式如下:

ifndef 标识符

程序段 1

else

程序段 2

endif

其意义是:如果未定义标识符,则编译“程序段1”;否则编译“程序段2”。

5.3使用#defined和#undef

与#ifdef类似的,可以在#if命令中使用define来判断是否已定义指定的标识符。例如:

if defined 标识符

程序段 1

endif

与下面的标示方式意义相同。

ifdef 标识符

程序段 1

endif

也可使用逻辑运算符,对defined取反。例如:

if ! define 标识符

程序段 1

endif

与下面的标示方式意义相同。

ifndef 标识符

程序段 1

endif

在#ifdef和#ifndef命令后面的标识符是使用#define进行定义的。在程序中,还可以使用#undef取消对标识符的定义,其形式为:

undef 标识符

Eg:

define MAX 100

……

undef MAX

在以上代码中,首先使用#define定义标识符MAX,经过一段程序代码后,又可以使用#undef取消已定义的标识符。使用#undef命令后,再使用#ifdef max,将不会编译后的源代码,因为此时标识符MAX已经被取消定义了。

六.其他预处理命令

6.1.预定义的宏名

   ANSI C标准预定义了五个宏名,每个宏名的前后均有两个下画线,避免与程序员定义相同的宏名(一般都不会定义前后有两个下划线的宏)。这5个宏名如下:

DATE:当前源程序的创建日期。
FILE:当前源程序的文件名称(包括盘符和路径)。
LINE:当前被编译代码的行号。
STDC:返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C.
TIME:当前源程序的创建时间。
Eg:

include

pragma命令的作用是设定编译器的状态,或者指示编译器完全一些特定的动作。#pragma命令对每个编译器给出了一个方法,在保持与C语言完全兼容的情况下,给出主机或者操作系统专有的特征。其格式一般为:

pragma Para

其中,Para为参数,可使用的参数很多,下面列出常用的参数:
Message参数,该参数能够在编译信息输出窗口中输出对应的信息,这对于源代码信息的控制是非常重要的,其使用方法是:

pragma message(消息文本)

当编译器遇到这条指令时,就在编译输出窗口中将消息文本显示出来。
另外一个使用比较多得pragma参数是code_seg.格式如:

pragma code_seg([“section_name”[,section_class]])

它能够设置程序中函数代码存放的代码段,在开发驱动程序的时候就会使用到它。
参数once,可保证头文件被编译一次,其格式为:

pragma once

只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。

6.4.产生错误信息命令 ————#error

error命令强制编译器停止编译,并输出一个错误信息,主要用于程序调试。其使用如下:

error 信息错误

注意,错误信息不用双括号括起来。当遇到#error命令时,错误信息将显示出来。
例如,以下编译预处理器命令判断预定义宏STDC,如果其值不为1,则显示一个错误信息,提示程序员该编译器不支持ANSI C标准。

if STDC!=1

#error NOT ANSI C
#endif

七.内联函数

在使用#define定义带参数宏时,在调用函数时,一般需要增加系统的开销,如参数传递,跳转控制,返回结果等额外操作需要系统内存和执行时间。而使用带参数宏时,通过宏替换可再编译前将函数代码展开导源代码中,使编译后的目标文件含有多段重复的代码。这样做,会增加程序的代码量,都可以减少执行时间。
在C99标准钟,还提供另外一种解决方法:使用内联函数。
在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替代。显然,这种做法不会产生转去转回得问题。都是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标代码量,进而增加空间的开销,而在时间开销上不像函数调用时那么大,可见它是以增加目标代码为代码来换取时间的节省。
定义内联函数的方法很简单,只要在定义函数头的前面加上关键字inline即可。内联函数的定义与一般函数一样。例如,定于一个两个整数相加的函数:

include

include

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值