简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程;
一般说来编译器会做以下几个过程:
- 预处理阶段
- 词法与语法分析阶段
- 编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件 (.obj文件)
- 连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,当然,最后还可以用objcopy生成纯二进制码,也就是去掉了文件格式信息。(生成.exe文件)
编译器在编译时是以C文件为单位进行的,也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件(.obj文件)为单位,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件。
在PC上的程序开发,一般都有一个main函数,这是各个编译器的约定;当然,你如果自己写连接器脚本的话,可以不用main函数作为程序入口!!!!
编译的过程为:(main .c文件 ···>目标文件···>可执行文件 )
有了这些基础知识,再言归正传,为了生成一个最终的可执行文件,就需要一些目标文件,也就是需要C文件,而这些C文件中又需要一个main函数作为可执行程序的入口,那么我们就从一个C文件入手,假定这个C文件内容如下:
//main.c
#include <stdio.h>
#include "mytest.h"
int main()
{
test = 25;
printf("test.................%d\n",test);
return 0;
}
头文件内容如下:
//mytest.h
int test;
现在以这个例子来讲解编译器的工作:
- 预处理阶段:编译器以C文件作为一 个单元,首先读这个C文件,发现第一句与第二句是包含一个头文件,就会在所有搜索路径中寻找这两个文件;
补充一个知识点:
#include <>格式:引用标准库头文件,编译器从标准库目录开始搜索;
#include ""格式:引用非标准库的头文件,编译器从用户的工作目录开始搜索.
找到之后,就会将相应头文件中再去处理宏,变量, 函数声明,嵌套的头文件包含等,检测依赖关系,进行宏替换,看是否有重复定义与声明的情况发生,最后将那些文件中所有的东东全部扫描进这个当前的C文件 中,形成一个中间“C文件”; - 编译阶段 :在上一步中相当于将那个头文件中的test变量扫描进了一个中间C文件,那么test变量就变成了这个文件中的一个全局变量,此时就将所有这个中间C文件的所有变量,函数分配空间,将各个函数编译成二进制码,按照特 定目标文件格式生成目标文件,在这种格式的目标文件中进行各个全局变量,函数的符号描述,将这些二进制码按照一定的标准组织成一个目标文件;
- 连接阶段 :将上一步成生的各个目标文件,根据一些参数,连接生成最终的可 执行文件,主要的工作就是重定位各个目标文件的函数