1 理解编译器
插曲: 编译型和解释型语言的区别(举一个形象的例子):编译型语言(c)是做完一锅的菜,然后才能开始吃饭;而解释型语言(python)就像吃火锅,边做饭边吃饭。
编译的过程: 预处理阶段——词法和语法分析阶段——编译阶段 ——连接阶段
源程序->词法分析器->语法分析器->语义分析器->中间代码生成器->代码优化器->代码生成器->目标程序
.
eg:
// main.c
#include <stdio.h> // 系统自带的头文件
#include "mytest.h"
int mian(int argc, char **argv)
{
test = 25;
printf("test............%d/n",test);
}
//main.h
int test;
我们来借助这个例子详细的讲一下编译的过程:
1.预处理阶段和语法分析阶段:编译器以C文件作为一个单元,首先读这个包含mian函数这个文件,发现第一局和第二句是包含头文件,就会在工程中的所有文件中寻找这两个头文件,找到之后,就会进入相应的头文件中。在头文件中干的事就是处理宏定义、变量、函数声明、嵌套的头文件等,检测依赖关系,进行宏替换,看是否存在重复定义与声明的情况发生,最后将mian.c中包含的头文件的所有信息都扫描进来,形成一个中间得mian.c文件。
2.编译阶段:上一步中将test变量扫描进入一个中间的C文件,这个test变量就变成了这个文件的一个全局变量,此时就将这个中间C文件中的所有变量、函数分配空间,将各个函数编译成二进制代码,按照特定文件格式生成目标文件。
3.连接阶段:将上一步成生的各个目标文件连接生成最终的可执行文件。
2 .*c and *.h
首先,我们要理解#include的用途,其等价于将头文件中的内容复制粘贴到*.c中
然后我们来看一下他们分别用于干什么:
*.h 文件: 宏定义、函数、变量的声明
*.c文件: 变量、函数的定义
容易出现的错误:
1. 在头文件中实现一个函数体,那么如果在多个C文件中引用它,而且又同时编译多个C文件,在每个引用此头文件的C文件所生成的目标文件中,都有一份这个函数的代码,就会发现多个相同的函数,就会报错。
2.如果在头文件中定义全局变量,并且将此全局变量赋初值,那么在多个引用此头文件的C文件中同样存在相同变量名的拷贝。
3.如果在C文件中声明宏,结构体,函数等,那么我要在另一个C文件中引用相同的宏,结构体,就必须再做一次重复的工作,如果我改了一个C文件中的一个声明,那么又忘了改其它C文件中的声明,这不就出了大问题了,程序的逻辑就变成了你不可想象的了,如果把这些公共的东东放在一个头文件中,想用它的C文件就只需要引用一下就OK了!!!这样岂不方便,要改某个声明的时候,只需要动一下头文件就行了。
4.在头文件中声明结构体,函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何利用你的库呢?也就是如何利用你的库中的各个函数呢??一种方法是公布源码,别人想怎么用就怎么用,另一种是提供头文件,别人从头文件中看你的函数原型,这样人家才知道如何调用你写的函数.
最后让我们来总结一下头文件存在的意义:
- 使得程序简明,清晰.
- 避免了重复编写相同的声明代码.
- *.c and *.h文件没有必然的联系.
网友评论