《嵌入式C编程:PIC单片机和C编程技术与应用》一第3章预编译指令3.1 标准预编译指令-阿里云开发者社区

开发者社区> 华章出版社> 正文
登录阅读全文

《嵌入式C编程:PIC单片机和C编程技术与应用》一第3章预编译指令3.1 标准预编译指令

简介:

本节书摘来自华章出版社《嵌入式C编程:PIC单片机和C编程技术与应用》一书中的第3章,第3.1节,作者 [美]马克·西格斯蒙德(Mark Siegesmund),更多章节内容可以访问云栖社区“华章计算机”公众号查看

第3章

Embedded C Programming: Techniques and Applications of C and PIC MCUS

预编译指令

编译器对C程序的处理可以明确地分为两步。第一步由预编译器完成。以#开头的预编译指令可能会影响编译器设置或者进行文本替换。注意,预编译器变量(标识符)和正常的C变量是不一样的。预编译完成之后,编译器将不会看到任何预编译指令或标识符。
下面介绍常见的预编译指令。

3.1 标准预编译指令

3.1.1 #define id text

id表示定义的名字
text表示替换的文本
使用时,程序中所有出现id的地方都会被替换成text。#define只做简单的文本替换。图3-1给出了#define的用法。这些#define称为宏。


b369f351be2a47d4c2fae51283596f8c29126de9

第一个define是很经典的用法,定义了一个常量,一般情况下,该常量在程序中很重要并且需要容易修改,或者在代码中出现了很多次。注意,数字15后面的注释。在预编译器处理代码之前,注释会被移除,否则后面代码中的;i=i+1){将会被当作注释而忽略掉。
第二个define使用了e3.h中定义的PIN_C6(#define PIN_C6 31766)。这两次替换都会在预编译期间完成。
第三个和第四个define演示了如何在define中使用语句,也演示了如何在一个define中使用其他define定义的内容。
在第六个define中,使用了数学算式,将其用小括号括起来是个好习惯。如果不这么做可能会造成不可预知的结果。例如,下列这些常量及其应用:


583cad3648465c8f6834ba07a3e7662607a0b60a

在预编译之后,会变成100 - 1×3,和预期的(100 - 1)×3完全不同。
预编译器并不了解C语言,它只是做简单的文本替换。这样可能会对错误分析造成一些困扰。假设在第六个define中,我们把乘号*错写成;,在编译时,下面这行会提示错误:


c09713aa326ba2b59df2a291693e1c3ea62010cc

本来这一行并没有问题。然而,由于这行使用了第六个输入错误的宏定义,在预编译时也被替换过来,结果就出现了错误提示。
注意,对于变量i,我们使用的是小写字母,而#define则使用的是全大写。这就是所谓的编程风格或者代码标准,可以方便区分#define符号。
预编译指令由#开始,占用一整行,但有时也需要占用多行。为了达到这个目的,需要使用反斜线。请看下面的例子:


23f23bc20f5896e9c35ec378b7cd337b882b8ef9

这种形式的#define看起来有点像函数调用。还有一种叫类函数宏或者带参数的宏,将在第13章中详细介绍。
编译器预定义了一些宏,例如:
date__和__time
它们分别定义了编译的日期和时间。

3.1.2 #include 或#include “filename”

在第1章中已经讲过#include。总的来说,它会将文件的内容插入代码中相应的位置。第一种形式中的<>表示编译器会首先在预定义的include文件目录中搜索文件,使用第二种形式""则会先在工程目录中搜索文件。<>通常用于包含编译器支持的文件,而""则用于工程相关的文件。无论哪种方式,都可以使用全路径:


1042d5b4d9fe5c42ece0b5104d81d275a02c420e

但是在删除工程文件时,这种做法会比较麻烦。
在IDE中,编译器的命令行或.ini配置文件都可以指定文件搜索目录列表。
每个工程中通常都会加入设备文件(如16F887.h)。例如,本书的例子中会加入e3.h这个设备文件。在include文件中还可以继续包含其他文件。

3.1.3 #ifdef、#ifndef、#else、#endif和#undef

ifdef是一种条件编译指令。除非在前面用#define定义过这个标识符,否则#ifdef和#endif之间的部分会被编译器忽略。例如:


e9d485fcb5c52eb2a782feaee52aae84f6903399

我们经常会看到类似的代码,只有当代码中存在下面这行时才会执行其中的打印操作。


36f159e19fdb45ad39ddf15b1ebf4fdf680fb216

注意,这里在标识符(DEBUG)之后没有任何文字。因为这种情况下我们不需要为DEBUG指定任何值。#else通常用在下列情况下:


dbfc2d04dd885e4a609c26146eec3f3d7efb61fe

ifdef是在编译阶段而不是在运行时起作用的,理解这一点非常重要。因此,上面的代码中只有一行reading=会被编译并放到芯片的内存中。在编译阶段已经决定了要编哪一行。这是个非常强大的工具,它让一份源代码只需要经过简单的配置就可以生成多个应用程序。#ifndef正好相反,当标识符没有定义的时候,会编译#ifndef之后的代码。#undef用来取消之前#define定义的标识符。

3.1.4 #if、#else、#elif和#endif

if和#ifdef类似,但#ifdef用来检测标识符有没有定义,而#if用来判断表达式是不是为真(非0)。标识符必须是宏而不是C变量。下面的例子中还用到了可选的#elif(else if):


51ac531b718eb91cf0ccc4849935a6e46094afe9

第5章将会详细介绍表达式。现在只需要知道==是用来测试是否相等的运算符,单个等号=是赋值运算符。

3.1.5 #error

编译程序时,只要遇到#error就会生成一个编译错误提示消息并停止编译,其后面跟的字符不用双引号包围,可以用来判断某段代码是否被编译过。使用#if和#include时,有时你认为应该编译的代码却没有编译,将#error放到应该被编译的代码中,如果没有抛出错误,那就说明这段代码被编译器忽略了。其中一个原因可能是什么地方少了一个#endif。很难发现#include文件中是否少一个#endif,因为被编译器忽略的代码在#include之后,两者不是同一个文件。
通常的用法如下:


63c14bb855d7b7689c56a393317d9ef7f2e37a33

如果某些宏的设置不对,导致#if的条件成立,编译就会停止。
在某些编译器中,如CCS C编译器,#error后面的文本会被预编译器替换。
例如,对于前面的#define示例,下面这行代码将会抛出下列错误(GREEN_LED的定义在e3.h中):


b0d8c90181711d26b981b708d4cf3bcb4b1a4ccb

3.1.6 #nolist和#list

nolist用来告诉编译器不要将这行输出到list文件(.lst)中。#list用来恢复正常操作。LST文件由编译器创建,用来显示每行C代码生成的汇编代码。这条预编译指令可以用来防止过长的注释或者数据定义在list文件中占用太多空间。编译器设备头文件就是用这种方法防止所有设备相关的#define出现在list文件中。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: