预编译指令的作用 预编译一般用来防止头文件的重复包含和编译。 在我们用C做开发的是候,有时候项目很大,我们所编写的程序会很长。这样我们如果还是写在一个文件中会出现管理上的问题和 查看上的不方便。因此,我们可以分多个文件编写我们的程序,这样把一个功能的程序写到一个文件里,便于查看 也有助于我们管理。 如main.c sd.h sd.c lcd.h lcd.c fat.h fat.c delay.h delay.c 至于头文件(sd.h)和源文件(sd.c)的写法应该是这样的: 在头文件中 我们写的是 函数的声明,和一些使用到的变量的声明; 在源文件中 我们写的是 对应于头文件中的函数的具体实现,即函数左大括号和右大括号中的内容。 在具体使用相关函数是可以使用#include 命令来包含其函数所在的头文件。下面我们虚拟如下的一个程序结构来展开说明: 如在main.c中 #include delay.h #include sd.h #include lcd.h #include fat.h main() { sd_init(); delay_nms(10); lcd_init(); while(1) { .... ....; } } 在sd.c中 void sd_init() { ...... ......; } 在sd.h中 #include delay.h #include ....... void sd_init(); ..... 在delay.c中 void delay_nms(UINT n) { for(....) {... _asm_("nop"); ... } } 在delay.h中 void delay_nms(UINT n); ..... 我们看,如果在头文件的书写中都没加预编译指令 #ifndef XXXXXXXX #define XXXXXXXX .... .... (头文件内容) .... #endif 会出现怎么样的情况???? 其实这样,编译会报错。编译器会提示 void delay_nms()定义了两次。为什么会出现这种情况呢? 我们顺着程序读一下,当我们在main.c中,编译器读到#include delay.h时会把 delay.h文件中的内容包含进来,这样delay.h中的 delay_nms()函数定义了一次,当我们在往下,读到#include sd.h时,把sd.h包含进来,在把sd.h的内容展开,我们又读到了一次delay.h 这样又把delay.h的内容包含近了main.c 。因此,我们发现delay.h被main.c重复包含了2次。这样使得void delay_nms()函数也被定义了二次。 如何解决重复定义的问题,我们是用条件编译,#ifndef ..等。在头文件中加入条件编译指令,如下: 在sd.h中 #ifndef _SD_H_ #define _SD_H_ #include delay.h #include ....... void sd_init(); ..... #endif 在delay.h中 #ifndef _DELAY_H_ #define _DELAY_H_ void delay_nms(UINT n); ..... #endif 这样,让我们再看下会出现什么情况。 当我们在main.c中,编译器读到#include delay.h时会把 delay.h文件中的内容包含进来,(#ifndef _DELAY_H_)我们先判断有没有定义 了 _DELAY_H_ 这个宏,由于这是第一次调用delay.h,_DELAY_H_这个宏还没定义,这样编译器会顺序向下执行,执行到#define _DELAY_H_ 则定义了一个_DELAY_H_宏,宏值为NULL. 当我们main.c中再往下,读到#include sd.h时,把sd.h包含进来,在把sd.h的内容展开, 我们又读到了一次delay.h。 这一次 我们判断 #ifndef _DELAY_H_ 结果是已经定义了,这样,编译器会跳过下面的内容,直接到 #endif。 可见,我们的函数 void delay_nms(UINT n); 就不会被重复定义二次! 对于其他的头文件,我们现在也加上 预编译指令,防止被重复包含。 另外,在WinAVR中,如果使用多文件编译,需要在Makeflie中修改 SRC = $(TARGET).c 在其后面添加你的头文件的源文件的名字。如 SRC = $(TARGET).c delay.c sd.c fat.c lcd.h 希望以上内容能对初学者有所帮助,有什么不对的地方也恳请大家指出修正。 PS:预编译指令常用到的是: 预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。常见的预编译指令有: (1)#include 指令 该指令指示编译器将xxx.xxx文件的全部内容插入此处。若用<>括起文件则在系统的INCLUDE目录中寻找文件,若用" "括起文件则在当前目录中寻找文件。一般来说,该文件是后缀名为"h"或"hpp"的头文件。 注意:<>不会在当前目录下搜索头文件,如果我们不用<>而用""把头文件名扩起,其意义为在先在当前目录下搜索头文件,再在系统默认目录下搜索。 (2)#define指令 该指令有三种用法: 第一种是定义标识,标识有效范围为整个程序,形如#define XXX,常与#if配合使用; 第二种是定义常数,如#define max 100,则max代表100(这种情况下使用const定义常数更好,原因见注1); 第三种是定义"函数",如#define get_max(a, b) ((a)>(b)?(a):(b)) 则以后使用get_max(x,y)就可以得到x和y中较大的数(这种方法存在一些弊病,见注2)。 (3)#if、#else和#endif指令 这些指令一般这样配合使用: #if defined(标识) //如果定义了标识 要执行的指令 #else 要执行的指令 #endif 在头文件中为了避免重复调用(比如说两个头文件互相包含对方),常采用这样的结构: #if !(defined XXX) //XXX为一个在你的程序中唯一的标识符, //每个头文件的标识符都不应相同。 //起标识符的常见方法是若头文件名为"abc.h" //则标识为"abc_h" #define XXX 真正的内容,如函数声明之类 #endif 注1:因为:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)。 注2:例如get_max(a++, b)时,a++会被执行多少次取决于a和b的大小!所以建议还是用内联函数而不是这种方法提高速度。虽然有这样的弊病,但这种方法的确非常灵活,因为a和b可以是各种数据类型