編譯鏈結原理 總述 編譯階段 鏈結階段 執行原理

2021-08-31 11:50:07 字數 3120 閱讀 6645

在平常的應用程式開發過程中,我們很少需要關注編譯和鏈結的過程,因為通常都是在整合的開發環境下執行,因此一般編譯和鏈結都是一步完成,通常將這種編譯和連線合併到一起的過程稱為構建。這樣雖然簡便,但是在這整個過程中,有時出現問題時,我們只能看到問題的表現,而很難看清本質性問題,所以對於這些一步完成的操作背後到底是怎樣的,我們需要深入了解,方便在以後遇到問題能後看清本質,快速解決。為什麼要對原始檔進行編譯鏈結生成最後的可執行檔案呢?一是機器只識別0/1**,二是原始檔在磁碟上儲存,要執行原始檔就必須將原始檔轉為機器可識別的二進位制檔案並將轉換後的檔案載入到記憶體中才可以。那麼首先是對於編譯鏈結原理的背後所作的事情的乙個簡單了解;

程式的執行過程分為兩大階段,編譯階段和鏈結階段,同時編譯階段又劃分為三步預編譯、編譯和彙編,再逼那一階段完成時,進行鏈結,那麼具體每部所做的內容如下:

一、預編譯(生成.i檔案)

巨集替換(刪除#define,並且展開所有的巨集定義)

遞迴展開標頭檔案(處理#include預編譯指令,將包含的檔案插入到該預編譯指令的位置)

刪除預編譯指令(處理所有的條件預編譯指令,例如"#if","#endif","#ifdef","dlif","#else"的等)

刪除注釋(刪除」//「和"/**/")

新增行號和檔案標識

保留#progma

二、編譯(生成.s檔案)

例如:int sum(int a,int b,int c = 10);

int sum(int a,int b = 20,int c);

int sum(int a,int b,int c);

這三行**是對sum函式的宣告,在進行語法分析的時候第二行是錯誤的,因為函式的預設值是從左向右依次進行賦值的。但在進行到語義分析時是正確的,結合上下文進行分析,第一句已經對c進行了預設值,在第二句進行分析時,c是有預設值的,這個值是10,然後再到b的預設值。所以是正確的。 

詞法分析 ;例如:int 1a = 1;(這裡是1不是小寫的』l『)//錯誤。定義變數只能以字母或下劃線開頭

語法分析;例如:int a = 10;delete a;  //錯誤 在編譯階段,會識別到delete後必須是指標,而這裡並不是指標,

語義分析(結合上下文進行分析);     

**優化

三、彙編(生成.o檔案,稱為可重定位的二進位制檔案)

在彙編階段,將**翻譯為二進位制指令後,通過在linux作業系統下,對彙編後的檔案進行檢視,得出,在彙編完成後還有以下事情未進行處理: 

(1)弱符號位置未進行處理(2)虛擬位址以及虛擬位移未進行處理(3)符號表中的外部符號進行處理

將 指令**翻譯成二進位制指令

四、鏈結(生成.exe檔案,稱為可執行的二進位制檔案)

合併段(相同段之間)和符號表

進行符號解析:在符號引用的地方找到符號定義的地方

分配位址和空間

符號的重定位

一、.o檔案

編譯階段經過預編譯、編譯和彙編處理後生成乙個.o檔案(以linux系統為例),又編譯器編譯源**後生成的檔案叫做目標檔案。則目標檔案就是源**編譯後但未進行連線的那些中間檔案(windows下的.obj和linux下的.o),它跟可執行檔案的內容和結構很相似,所以一般和可執行檔案採用同一種格式儲存。也就是從結構上來說,目標檔案是已經變異後的可執行檔案,知識沒有經過鏈結階段,其中有些符號或者位址沒有被調整。

二、目標檔案的內容以及存放

那麼目標檔案中至少有編譯後的機器指令**、資料。當然,除了這些內容外,目標檔案中還包含了鏈結時所需要的一些資訊,例如符號表、除錯資訊、字串等。一般目標檔案將這些資訊按照不同的屬性按「段」的形式進行儲存。程式源**編譯後的機器指令經常被放在**段裡,即「.text」中。全域性變數和區域性靜態變數資料經常放在資料段,即".data"中。未初始化的全域性變數和區域性靜態變數放在.bss段中。

四、.bss段

.bss段儲存的是未定義的全域性變數和區域性靜態變數。但.bss段只是為未初始化的全域性變數和區域性靜態變數預留位置而已,他並沒有內容,所以他在檔案中也不佔據空間。在這裡我們引入強弱符號(c語言中,只關心全域性變數)的概念:

強符號:已初始化的全域性變數;

弱符號:未初始化的全域性變數;

強弱符號的使用規則:

兩強符號:重定義錯誤

一強一弱符號:選擇強符號

兩弱符號:選取位元組數較大的

三、指令段和資料段

在編譯階段結束會,生成可重定位的二進位制檔案即目標檔案,將檔案中的指令資料等資訊分別按照屬性儲存在虛擬位址空間中,資料區域對程序來說是可讀寫的,而指令段對與程序而言只是可讀的,所以這兩個區域的許可權是可讀寫和唯讀。這樣就會防止指令被有意無意的篡改,同時當程式執行多個該程式的副本時,它們的指令是相同的,所以記憶體中只須要儲存乙份該程式的指令部分。並且分開儲存有利於提高cpu的快取命中率。 

一、 在編譯階段完成後生成.o的目標檔案,進入到鏈結階段,對於鏈結器來說,整個鏈結過程就是將幾個輸入目標檔案加工合併成乙個輸出檔案。這裡的輸入檔案是目標檔案即.o檔案,輸出檔案是可執行的二進位制檔案。鏈結後的檔案儲存和目標檔案的儲存一樣,都是將不同的資訊屬性存放到對應的段中,唯一不同的是可執行的二進位制檔案會對一些符號進行解析,調整一些位址等;

那麼,在鏈結階段具體都要做些什麼呢?

1.符號表和段合併:將相同性質的段合併到一起

2.符號解析:在符號引用的地方找到符號定義的地方

3.分配位址和空間

4.符號的重定位: 處理虛假偏移量

在編譯鏈結階段結束後,也就是生成了 可執行的二進位制檔案;但該檔案並不能直接進行執行,因為此時的檔案並未在記憶體中,也就是說,作業系統在執行乙個程式時,需要指令和資料,並且必須將所要執行的程式載入到記憶體上;

那麼,在執行時,需要做以下的事情:

建立虛擬位址和物理內的對映結構體;按照段頁式進行對映,以4k大小對齊;

load載入器,將指令和資料載入到記憶體中;

將第一行指令的位址寫入pc暫存器中;

ps:本文中的測試,均在linux下進行;

編譯鏈結原理

從源 到可執行程式,需要經歷以下幾個過程 預處理 編譯 彙編 連線。1.預處理 預處理主要是處理以 開頭的預編譯指令,包括 include define if等 刪除注釋 新增行號以及標頭檔案展開。2.編譯 編譯的主要工作是詞法分析 語法分析 優化編譯,將源 翻譯成彙編 3.彙編 彙編是將彙編 翻譯...

編譯鏈結原理

32位計算機,每個程式都有4g的虛擬位址空間。首先虛擬位址空間分為兩大塊,乙個是使用者空間,乙個是核心空間。使用者空間佔3g的大小,並且它是每個程序所獨有的,它的開頭128m存放的是我們無法訪問的地方。1 預編譯 生成 i 檔案 操作命令 gcc e main.c o main.i 具體內容 1 巨...

編譯鏈結原理

32位計算機,每個程式都有4g的虛擬位址空間。首先虛擬位址空間分為兩大塊,乙個是使用者空間,乙個是核心空間。使用者空間佔3g的大小,並且它是每個程序所獨有的,它的開頭128m存放的是我們無法訪問的地方。text c語言的編譯後執行語句都編譯成機器 儲存在.text段 data 已初始化的全域性變數和...