對於代碼的編譯問題千頭萬緒從何說起呢,首先來說一下計算機是如何處理應用程序的,實質上應用程序是通過操作系統來應用機器指令操控硬件設施完成各種任務的,就從編譯的環節開始談起吧,眾所周知,程序開發人員所寫的代碼實際上計算機是沒有辦法去認識的,那么就必須通過編譯將其轉換為計算機可以認識的機器指令,在有操作系統根據具體指令從硬件上分配內存處理程序段。以下從預編譯,編譯,匯編,鏈接,來簡單的說一下程序的編譯過程。
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 |
|
|
當所有的符號變量都能夠找到合法的內存地址時,鏈接階段重定位完成。