嵌入式c语言头文件的建立与使用
如何正确编写C语言头文件和与之相关联的c源程序文件,这首先就要了解它们的各自功能。要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程。
一般说来编译器会做以下几个过程:
1.预处理阶段
2.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件(.obj文件)
3.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件。
编译器在编译时是以C文件为单位进行的,也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件。
为了生成一个最终的可执行文件,就需要一些目标文件,也就是首先要有C文件,而这些C文件中又需要一个main()函数作为可执行程序的入口,那么我们就从从这一个C文件入手,引入头文件概念。
假定这个C文件内容如下:
#include<stdio.h>
#include"mytest.h"
int main(int argc,char**argv)
{
test=25;
printf(" %d\n",test);
}
头文件"mytest.h"包含如下内容:
int test;
现在以这个例子来讲解编译器的工作:
1.预处理阶段:编译器以C文件作为一个单元,首先读这个C文件,发现第一句与第二句是包含一个头文
件,就会在所有搜索路径中寻这两个文件,到之后,就会将相应头文件中的宏,变量,函数声明,嵌套的头文件包含等,进行依赖关系检测,并进行宏替换,看是否有重复声
明与定义的情况发生,最后将那些文件中所有的东东全部扫描进这个当前的C文件中,形成一个中间"C文件"
2.编译阶段,在上一步中相当于将第二个头文件中的test变量扫描进了一个中间C文件,那么test变量就变成了这个文件中的一个全局变量,此时就将所有这个中间C文件的所有变量,函数分配空间,将各个函数编译成二进制码,按照特定目标文件格式生成目标文件,在这种格式的目标文件中进行各个全局变量,函数的符号描述,将这些二进制码按照一定的标准组织成一个目标文件
3.连接阶段,将上一步成生的各个目标文件,根据一些参数,连接生成最终的可执行文件,主要的工作就是重定位各个目标文件的函数,变量等,相当于将个目标文件中的二进制码按一定的规范合到一个文件中。
再回到C文件与头文件各写什么内容的话题上:一般都在头件中进行函数声明,变量声明,宏声明,结构体声明呢?而在C文件中去进行变量定义,函数实现。理论上来说C文件与头文件里的内容,只要是C语言所支持的,无论写什么都可以的,比如你在头文件中写函数体,只要在任何一个C文件包含此头文件就可以将这个函数编译成目标文件的一部分(编译是以C文件为单位的,如果不在任何C文件中包含此
头文件的话,这段代码就形同虚设),你可以在C文件中进行函数声明,变量声明,结构体声明,这也不成问题!!!那为何一定要分成头文件与C文
件呢?又为何一般都在头件中进行函数声明,变量声明,宏声明,结构体声明呢?而在C文件中去进行变量定义,函数实现呢?原因如下:1.如果在头文件中实现一个函数体,那么如果在多个C文件中引用它,而且又同时编译多个C文件,将其生成的目标文件连接成一个可执行文件,在每个引用此头文件的C文件所生成的目标文件中,都有一份这个函数的代码,如果这段函数又没有定义成局部函数,那么在连接时,就会发现多个相同的函数,就会报错
2.如果在头文件中定义全局变量,并且将此全局变量赋初值,那么在多个引用此头文件的C文件中同样存在相同变量名的拷贝,关键是此变量被赋了初值,所以编译器就会将此变量放入DA TA段,最终在连接阶段,会在DA TA段中存在多个相同的变量,它无法将这些变量统一成一个变量,也就是仅为此变量分配一个空间,而不是多份空间,假定这个变量在头文件没有赋初值,编译器就会将之放入BSS段,连接器会对BSS段的多个同名变量仅分配一个存储空间
3.如果在C文件中声明宏,结构体,函数等,那么我要在另一个C文件中引用相应的宏,结构体,就必须再做一次重复的工作,如果我改了一个C文件中的一个声明,那么又忘了改其它C文件中的声明,这不就出了大问题了,程序的逻辑就变成了你不可想象的了,如果把这些公共的东东放在一个头文件中,想用
它的C文件就只需要引用一个头文件就行了,要改某个声明的时候,只需要动一下头文件就行了这样岂不方便。
再说头文件,头文件是一种文本文件,使用文本编辑器将代码编写好之后,以扩展名.h 保存就行了。如上所述头文件中一般放一些重
复使用的代码,例如函数声明,变量声明,常数定义,宏的定义等等。在实际编程中,我们在需调用该c文件相对应的头文件用#include语句将头文件包含进来引用时,也就是相当于将头文件中所有内容复制到#include处。
printf函数原型在什么头文件里为了避免因为重复引用而导致的编译错误,头文件常具有
#ifndef LABEL
#define LABEL
……….. //代码部分
#endif
的格式。其中,LABEL 为一个唯一的标号,命名规则跟变量的命名规则一样。常根据它所在的头文件名来命名,例如,如果头文件的文件名叫做hardware.h ,那么可以这样使用:
#ifndef__HARDW ARE_H__
#define__HARDW ARE_H__
….....  //代码部分
#endif
这样写的意思就是,如果没有定义__HARDWARE_H__ ,则定义
__HARDWARE_H__ ,并编译下面的代码部分,直到遇到#endif。这样,当重复引用时,由于__HARDWARE_H__ 已经被定义,则下面的代码部