对于代码的编译问题千头万绪从何说起呢,首先来说一下计算机是如何处理应用程序的,实质上应用程序是通过操作系统来应用机器指令操控硬件设施完成各种任务的,就从编译的环节开始谈起吧,众所周知,程序开发人员所写的代码实际上计算机是没有办法去认识的,那么就必须通过编译将其转换为计算机可以认识的机器指令,在有操作系统根据具体指令从硬件上分配内存处理程序段。以下从预编译,编译,汇编,链接,来简单的说一下程序的编译过程。
2.1编译预处理
在这个阶段主要是宏定义的展开,以及头文件的递归处理,即展开所有的以#开头的编译命令。
2.2编译阶段
将程序代码段按字符流格式进行切割,处理,主要是词法分析,语法分析,语义分析等阶段,编译完成后生成中间代码。
2.3汇编
将编译后的中间代码通过汇编器模块生成计算机能够识别的机器指令用以操控硬件设施生成目标代码(可重定位目标代码)。
2.4链接
通过链接器模块将各种目标代码以及库文件(*.lib文件),资源文件(*,rec)进行链接处理最终生成可以执行的*.exe文件。
2.5重定位问题
通过一个例子来看:假如我们有两个头文件和两个源文件分别叫做function1.h和function2.h以及function1.cpp和function2.cpp文件其中function1.h内容如下
Function1.h
#ifndef _FUNCTION1_H
#define _FUNCTION1_H
Int g_val;
Int Add(int m, int n);
#endif
Function1.cpp
g_val=10;
Int Add(int m, int n)
{
Return m+n;
}
Function2.cpp其中包含了main函数内容如下
#include “function1.h”
Int main()
{
Int l_valfri=3;
Int l_valsec=4;
g_val=14;
Int result=Add(l_valfri,l_valsec);
Return 0;
}
对于这样的代码编译器在编译function2.cpp时对于外部符号g_val 和外部函数Add该如何决议呢,这里又会涉及到可重定位文件中的符号表问题。
其实在可重定位目标文件之中会存在一个用来放置变量和其入口地址的符号表,当编译过程中能够找到该符号的定义时就将该符号入口地址更新到符号表中否则就对该符号的地址不做任何决议一直保留到链接阶段处理。通过两个例子来看符号表的结构。
在编译过程中function1.cpp文件的可重定位目标文件中的符号表如下
变量名 | 内存地址 |
g_val | 0x100 |
Add | 0x200 |
|
|
为什么可以做到对于符号g_val和Add分配内存地址呢,因为在编译阶段就能够在function1.cpp文件中找到他们的定义,所以能够进行明确的内存地址分配。
再来看看function2.cpp所生成的可重定位目标文件的结构:
变量名 | 内存地址 |
g_val | 0x00 |
Add | 0x00 |
为什么会出现这样的状况。因为在编译阶段虽然可以看到这些符号变量的声明,但却找不到他们的定义所以编译器陷入了一个决而未决的境地。
将包含文件展开时,function2.cpp大概会是这个样子很明显只有符号变量的声明但是没有定义。
#ifndef _FUNCTION1_H
#define _FUNCTION1_H
Int g_val;
Int Add(int m, int n);
#endif
Int main()
{
Int l_valfri=3;
Int l_valsec=4;
g_val=14;
Int result=Add(l_valfri,l_valsec);
Return 0;
}
先将他们存放在符号表中但却不去为他们进行内存关联一直等到链接阶段在进行处理。
重定位发生于目标代码链接阶段,在链接阶段链接器就会查找符号表,当他发现了function2.cpp的符号表之中任然有没有决议的内存地址时,链接器就会查找所有的目标代码文件,一直到他找到了function1.cpp所生成的目标代码文件符号表时发现了这些没有决议的符号变量的真正内存地址,这是function2.cpp所生成的目标代码文件就会更新它的符号表,将这些尚未决议的符号变量的内存地址写进其符号表中。
更新之后的function2.obj文件符号表
变量名 | 内存地址 |
g_val | 0x100 |
Add | 0x200 |
|
|
当所有的符号变量都能够找到合法的内存地址时,链接阶段重定位完成。