在嵌入式系统编程中,无论是内核驱动还是应用程序,都会涉及到大量的预处理和条件编译。 这样做的好处主要体现在代码的可移植性强以及代码修改的方便。 因此,引入了预处理和条件编译的概念。 C语言的程序中可能包含各种以#开头的编译指令,这些指令称为预处理命令。 预处理命令属于C语言编译器,不是C语言的组成部分。 C语言编程环境可以通过预处理命令进行扩展。 预处理的行为由指令控制。 这些指令是以#字符开头的命令。
1. 预处理指令
大多数预处理器指令分为以下 3 种类型:
●宏定义:#指令定义宏,#undef指令删除宏定义。
● 文件包含:# 指令使指定文件的内容包含到程序中。
●条件编译:#if、#ifdef、#、#elif、#else 和#endif 指令可以根据编译器可以测试的条件将一段文本包含到程序中或从程序中排除。
其余的#error、#line 和# 指令是比较特殊的指令,很少使用。
2. 宏定义
1.无参数宏
# 宏名称字符串
●源程序中的宏名称只能用引号引起来,预处理程序不会将其替换为宏。
●宏定义允许嵌套,定义的宏名称可以在宏定义字符串中使用。 在宏展开时由预处理器逐层替换。
●通常,宏名称可以用大写字母表示,以区别于变量。 但是,也允许使用小写字母。
2. 带参数的宏
# 宏名称(形参列表)字符串
定义带参数的宏时,宏名称和形参列表之间不能有空格,否则宏将被定义为不带参数的形式,从而导致程序错误。
例如:# ABS(x) (x)
3. 预处理运算符#和##
使用#定义宏时,可以使用#运算符以字符串形式输出实际参数。 例如:
# AREA(x,y)("长为"#x"、宽为"#y"的矩形面积:%d\n",(x)*(y));
##与#操作符类似,##操作符也可以用来将宏中的部分内容替换为参数。 该运算符将宏中的两个部分连接成一个内容。 例如,定义以下宏:
# VAR(n) v##n
当引用宏时使用:
自变量(1)
3.该文件包含
#<文件名>
#“文件名”
它的扩展名可以是“.c”,表明它包含普通的C语言源程序。 也可以是“.h”,表示C语言程序的头文件。 C语言系统中大量的定义和声明都是以头文件的形式提供的。
用 括起来的文件名,一般指系统头文件,编译器会在C语言开发环境的文件集中查找指定的文件。
文件名用“”括起来是自定义头文件
四、条件编译
1、单条件判断
#如果
节目段
#别的
节目段
#万一
条件编译命令的执行过程为:如果常量表达式的值为真(不为0),则编译程序段1,否则编译程序段2。 因此,程序可以在不同的条件下执行不同的功能。
2、多条件判断
#if常量表达式1
节目片段1
#elif常量表达式2
节目片段2
...
#elif 常量表达式 n
区块 n
#别的
块米
#万一
3.使用#ifdef和#
判断符号常数定义
#ifdef命令的使用格式如下:
#ifdef 标识符
节目片段1
#别的
节目片段2
#万一
4.使用#和#undef
与#ifdef类似,可以用在#if命令中来判断指定的标识符是否已被定义。
#如果标识符
节目片段1
#万一
它与下面的符号具有相同的含义。
#ifdef 标识符
节目片段1
#万一
逻辑运算符也可用于求反。 例如:
#如果 ! 标识符
节目片段1
#万一
它与下面的符号具有相同的含义。
# 标识符
节目片段1
#万一
#ifdef 和# 命令后面的标识符使用# 定义。 在程序中,还可以使用#undef取消标识符的定义,形式为:
#undef 标识符
例如:
# 最多 100
#undef 最大
五、其他预处理命令
1. 预定义宏名称
ANSI C标准预定义了5个宏名,每个宏名前后各有两个下划线,以避免程序员定义相同的宏名(一般情况下,前后各有两个下划线的宏是不定义的)。 五个宏名称如下:
●:当前源程序的创建日期。
● :当前源程序的文件名(包括盘符和路径)。
● :当前编译代码的行号。
● :返回编译器是否为标准C,如果其值为1,则表示符合标准C,否则不是标准C。
● :当前源程序的创建时间。
例如:
#
int main()
整数j;
(“日期:%s\n”,);
("时间: %s\n",};
("文件名:%s\n",);
("这是第 %d\n 行",);
("此编译器 %s 标准 C\n",()? "符合": "不符合");
0;
2.行号和文件名命令#line
使用预定义宏名称编译的程序的行号。 使用#line 命令更改预定义宏的内容。 命令的基本形式如下:
#线[””]
其中数字是正整数,可选文件名是有效的文件标识符。 行号是源代码中的当前行号,文件名是源文件的名称。 命令#line主要用于调试和其他特殊应用。
例如:
1:#
2:#
4:#第 1000 行
6: int main()
7:{
8: ("当前行号:%d\n",);
9:0;
10:}
3.修改编译器设置命令#
#命令的作用是设置编译器的状态,或者指示编译器完成一些特定的动作。 # 命令为每个编译器提供了一种方法,赋予主机或操作系统特定的功能,同时保持与 C 语言的完全兼容性。 其格式一般为:
#帕拉
其中Para是一个参数,可以使用的参数有很多。 常用参数如下:
参数,可以在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制非常重要,其使用方法为:
#(消息文本)
当编译器遇到该指令时,它会在编译输出窗口中显示消息文本。
另一个使用更频繁的参数是。 格式如下:
# ([“”[,]])
可以设置程序中存放功能代码的代码段,在开发驱动程序时会用到。
参数once可以保证头文件编译一次,其格式为:
#一次
只需在头文件的最开头添加此指令即可确保头文件被编译一次。
4. 生成错误信息命令#error
#error命令强制编译器停止编译并输出错误信息,主要用于程序调试。 它的使用方法如下:
#error 信息错误
请注意,错误消息未括在双括号中。 当遇到#error命令时,会显示错误信息。
例如,下面的编译预处理器命令对预定义宏进行判断,如果其值不为1,则会显示错误信息,提示程序员编译器不支持ANSI C标准。
#如果!=1
#错误不是 ANSI C
#万一
6. 内联函数
使用#带参数的宏时,在调用函数时,一般需要增加系统开销,如参数传递、跳转控制、返回结果等额外操作需要系统内存和执行时间。 当使用带参数的宏时,可以通过宏替换将功能代码展开并导入到源代码中,然后重新编译,从而使编译后的目标文件包含多个重复代码。 这样做会增加程序的代码大小并减少执行时间。
在C99标准时钟中,提供了另一种解决方案:使用内联函数。
当程序编译时,编译器将程序中出现的内联函数的调用表达式替换为内联函数的函数体。 显然,这种做法不会造成来回的问题。 都是因为编译时将函数体中的代码替换到程序中,所以会增加目标代码量,空间开销也会增加,而时间开销却没有函数那么大称呼。 可见是增加了目标代码。 代码换代码,以换取时间的节省。
定义内联函数的方法很简单,只要在定义函数头前面添加关键字即可。 内联函数的定义与普通函数的定义相同。 例如,给定一个将两个整数相加的函数:
#
#
int add(int x,int y);
int add(int x,int y)
x+y;
int main()
整数 i,j,k;
("请输入两个整数的值:\n");
scanf("%d %d",&i,&j);
k=加(i,j);
("k=%d\n",k);
0;
程序中,当调用函数add时,该函数会在编译时复制上述代码,而不是像普通函数一样在运行时调用。
内联函数具有一般函数的特点,它与一般函数的区别在于函数调用的处理。 当调用一般函数时,需要将程序的执行权转移给被调用函数,然后返回到调用它的函数; 当调用内联函数时,调用表达式将替换为内联函数的主体。 使用内联函数时应注意以下几点:
● 内联函数内允许使用循环语句和 语句。
● 内联函数的定义必须出现在第一次调用内联函数之前。
事实上,当在程序中将函数声明为内联时,该函数在编译后并不一定是内联的。
即程序只是建议编译器使用内联函数,但编译器会根据函数来决定是否使用内联,所以如果编写的内联函数中有循环或语句,程序不会提示错误,但该函数不再是内联函数。
一般来说,小函数都用作内联函数。
如果您对编程感兴趣,想了解更多编程知识、解决编程问题、咨询编程学习,可以关注我们的微信公众号:程序员互动联盟(),这里有java高手、C++/C高手、/Linux专家正在等你。