我的部落格
本文介紹c
編譯過程,即,如何從原始碼生成可執行檔案,目標程式。
第乙個階段是編譯compilation
。
編譯器為定義內容分配記憶體,從程式語句生成操作碼,生成乙個可重分配的目標檔案.o
,彙編器也會將組合語言原始檔生成.o
檔案。
編譯器一次完成乙個轉換單元的工作,乙個轉換單元是乙個通過預處理的.c
檔案。
編譯器以及彙編器建立可重分配的目標檔案.o
。
乙個庫設施可能會用來將目標檔案組合到庫檔案。
編譯是乙個多階段過程,每乙個過程都使用上乙個階段的輸出,編譯器自身可以分為三個部分:
第一階段
預處理預處理解析原始碼檔案,評估預處理指令 (以#
開始),比如#define
。
移除空額
c
忽略空格,所以第一階段處理會將所有的空格移除。
標誌c
程式由標誌組成,這個標誌可能是
語法分析
語法分析確保標誌按照語法規則以正確的方式組合。如果沒有,那麼編譯器將會在這個階段生成語法錯誤提示。語法分析的輸出是被稱作parse tree
的結構體。
中間**
這個階段生成的**是不依賴於裝置的等效程式,稱為intermediate representation: ir
,這個程式從parse tree
生成。
ir
允許編譯器支援多個不同的語言,支援多個不同的目標。
中間階段
語義分析
語義分析新增更多的予以資訊到ir ast
中,並檢查程式的邏輯結構。現在的編譯器通常具備類似如下的功能,比如檢測未使用的變數,使用了未初始化的變數等。在這個階段發現任何問題,都會生成警告資訊,而不是錯誤資訊。
在這個階段構建程式符號表,並插入debug
資訊。
優化常用的優化包括函式的內聯展開,移除無效**,暫存器分配等。
最後階段
**生成
將優化後的**生成目標平台的操作碼。
c
編譯器在段內為**與資料分配記憶體。每乙個段包含乙個不同的資訊型別。段可能通過名稱或屬性確定。這個屬性資訊被鏈結器用來在記憶體內分配段資訊。
**由編譯器生成的操作碼儲存到它們自己的記憶體段內,通常為.code
或.text
。
靜態資料
靜態資料區通常可以劃分為兩個子段:
未初始化的段通常為.bss
或zi
,初始化定義的段位.data
或rw
。
常量常量肯恩夠以兩種形式給出:
傳統的c
模型將使用者定義的常量目標放到.data
段。
字面值通常放到.text
/.code
段。
現在大部分c
工具鏈支援.const
/.rodata
段來指定常量值。這個段可以被放到rom
中。
自動化變數
大部分的變數定義在函式、類內,作為自動化變數。這個變數也包括任何從非void
函式中返回的臨時返回值,以及函式引數。
對於這些程式設計物件,分配到棧中。對於函式引數以及返回值記憶體通常由呼叫函式分配 (通過將值入棧),對於區域性物件,在函式被呼叫時,分配使用的記憶體。這個關鍵特性確保函式能夠遞迴呼叫。
值得注意的是編譯器不會建立乙個.stack
段。相反,會生成訪問相關暫存器 (棧指標) 的操作碼,棧指標在程式啟動後會指向棧部分的頂部。
在現在大部分的微控制器中,尤其是32
位的risc
架構中,自動化變數被放到分離的暫存器中,而不是棧中。比如ram
架構的流程呼叫標準aapcs
定義cpu
暫存器用來存放函式呼叫的引數,以及返回的結果,還有函式的區域性變數。
動態資料
動態物件的記憶體是從堆中分配的,與棧類似,堆不是由編譯器在編譯時分配的,兒實在鏈結階段分配的。
編譯器生成目標檔案.o
檔案。
目標檔案包含編譯後的原始碼,操作碼以及資料段。注意到,目標檔案只包含靜態變數的段。在則會個階段,段位置不固定。
.o
檔案還不能被執行。雖然已經有了指令操作碼,pc
指標相關位址,立即數等;但是靜態及全域性位址只是以它們相關段的起始位址的偏置。再其他模組中定義的位址完全不知道。目標檔案包含兩個表:imports
以及exports
。
鏈結器將目標檔案組合到單個的可執行程式,為了完成這個工作,它必須完成多個任務。
如果任何引用沒有被解析,那麼會查詢所有指定的 庫/文件(.a) 檔案,以及適當的模組,來解析這些引用。這是乙個迭代的過程。在這之後,鏈結器如果還是不能解析乙個符號,它會報錯unresolved reference
。
c
標準指定所有的外部目標必須在所有的目標檔案中至少具有乙個定義。
為了能夠執行**,資料段必須放到記憶體的絕對位置。每乙個段在記憶體中都具有乙個絕對位址。通常,在非易失儲存裝置上具有乙個基位址,來存放恆久存在的段 (比如**);在易失儲存裝置上具有乙個位址,來存放臨時段 (比如棧)。
在乙個嵌入式系統上,任何經過初始化了的資料必須儲存在非易失儲存裝置上 (flash/rom)。在啟動時任何非常量資料必須被複製到ram
中。非常常見的是將唯讀段 (比如**) 複製到ram
中,來加速執行。
為了實現這個功能,聯結器必須建立額外的段來實現從rom
到ram
的複製。每乙個需要通過複製初始化的段被劃分為兩個,乙個為rom
部分 (可用於初始化部分),乙個為ram
部分 (執行時位置)。鏈結器生成的初始化段通常稱為shadow data
段比如.sdata
。
.bss
段也位於ram
中,但是不在rom
中有乙個shadow
複製。這個段可以通過演算法初始化。
聯結器操作的細節可以通過在命令列下呼叫選項控制 (通過鏈結器控制檔案 lcf)。
你可能以另乙個檔名認識了這個檔案,比如鏈結指令碼,鏈結配置檔案等。lcf
檔案定義物理記憶體布局 (flash/sram) 以及不通程式區的放置。lcf
的語法是高度依賴編譯器的,因此具有它自己的格式,雖然扮演的角色通常是一樣的。
當使用乙個ide
時,這些選項通常可以非常使用者友好的的指定。ide
生成需要的指令碼以及呼叫選項。
要控制的最重要的事是最後記憶體段的位置。必須尊重硬體裝置的記憶體布局,對於處理器而言,它希望自己想要的內容在指定的位置。
之後lcf
指定棧與堆的大小。通常會將堆放到ram
的低位址位置,棧放到ram
的高位址位置,來盡量避免這兩個區域衝突 (堆會向上生長,棧會向上生長)。
例子略
鏈結器會進行檢查來確保你的**段與資料段會進入到設計好的記憶體位置。
定位過程的輸出是乙個load
載入檔案,以獨立於平台的格式給出,通常是.elf
或.dwarf
等。
elf
檔案也會被偵錯程式在原始碼除錯時使用。
elf
為與目標無關的格式的檔案。為了載入到目標裝置上,elf
檔案必須轉化到flash/prom
的格式 (.bini
或.hex
)。
c編譯過程
編譯的概念 編譯程式讀取源程式 字元流 對之進行詞法和語法的分析,將高階語言指令轉換為功能等效的彙編 再由匯程式設計序轉換為機器語言,並且按照作業系統對可執行檔案格式的要求鏈結生成可執行程式。編譯的完整過程 c源程式 預編譯處理 c 編譯 優化程式 s asm 匯程式設計序 obj o a ko 鏈...
c 編譯過程
編譯過程主要分為 4個過程 1 編譯預處理 預編譯程式完成的工作,可以說成是對源程式的 替換 工作。經過這個過程,生成乙個沒有巨集定義 沒有條件編譯指令 沒有特殊符號的輸出檔案。2 編譯 優化階段 通過詞法分析 語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間 或彙編 在c 中,以...
C 編譯過程
以helloword.c 程式說明編譯過程 在預設的狀態下,如果我們直接以gcc編譯原始碼,並且沒有加上任何引數,則執行檔案的檔名會被自動設定為a.out 這個檔名。所以你就能夠直接執行 a.out這個這行檔案。hello.c 就是原始碼,gcc是編譯程式,a.out 是編譯成功的可執行檔案。如果我...