程序員必須了解的------編譯與連接

在 Xcode 中按下 command + B
不出意外的話, 就會顯示小錘子
小錘子下面是 Build Succeeded
Build 就是構(gòu)建
其實構(gòu)建是一個很復(fù)雜的過程
大致的分為 預(yù)處理 編譯 匯編 鏈接 四個步驟
最近看了 程序員的自我修養(yǎng) 這本書和一些相關(guān)的博客
整理一下構(gòu)建過程的細(xì)節(jié)


預(yù)編譯

在構(gòu)建的第一步是預(yù)編譯
預(yù)編譯做的事比較簡單, 大概是:

  • 將所有的 #define 刪除, 并且展開所有宏定義
  • 處理所有預(yù)編譯指令, 比如 #if #ifdef #elif #else #endif
  • 處理 #include 預(yù)編譯指令, 將被包含的文件插入到該預(yù)編譯指令位置
  • 刪除所有注釋 /* */ //
  • 添加行號和文件標(biāo)識, 以便于編譯時編譯器產(chǎn)生調(diào)試用的行號信息以及用于編譯時產(chǎn)生編譯錯誤或警告時顯示行號
  • 保留 #pragma 指令, 編譯器會用到他們

編譯

編譯過程就是將預(yù)處理完的文件進(jìn)行一系列操作 : 詞法分析 語法分析 語義分析 優(yōu)化并生成匯編代碼
這個過程是構(gòu)建程序最核心的部分, 也是最復(fù)雜的部分.

  • 詞法分析

    首先源代碼被輸入到掃描器, 掃描器進(jìn)行詞法分析, 運用一種類似有限狀態(tài)機(jī)的算法將源代碼分割成記號
    例如這段代碼 array[index] = index + 4 會被分割成這樣 :
記號 類型
array 標(biāo)識符
[ 左方括號
index 標(biāo)識符
] 右方括號
= 賦值
( 左圓括號
index 標(biāo)識符
+ 加號
4 數(shù)字
) 右圓括號
* 乘號
( 左圓括號
2 數(shù)字
+ 加號
6 數(shù)字
) 右圓括號

詞法分析產(chǎn)生的記號一般分為以下幾類 : 關(guān)鍵字 標(biāo)識符 字面量(數(shù)字 字符串) 特殊符號(加號 等號),
與此同時, 掃描器還完成了其他工作 : 將標(biāo)識符存放到符號表, 將數(shù)字字符串常量存放到文字表等.

  • 語法分析

    接下來語法分析器將對掃描器產(chǎn)生的記號進(jìn)行語法分析, 從而產(chǎn)生語法樹, 語法樹是以表達(dá)式為節(jié)點的樹 :

    語法樹

    圖中可以看出, 符號和數(shù)字是最小表達(dá)式, 他們不是有其他表達(dá)式組成的, 所以他們通常作為整個語法樹的葉節(jié)點.
    在語法分析的同事, 很多運算符號的優(yōu)先級也被定義下來, 比如乘法優(yōu)先級比加法要高, 還有一些符號有多重含義, 比如*既可以代表乘法也可以代表對指針取內(nèi)容, 語法分析階段會對這些內(nèi)容進(jìn)行區(qū)分, 如果出現(xiàn)不合法的表達(dá)式, 編譯器會報錯.

  • 語義分析

    語義分析由語義分析器完成, 語法分析僅是對表達(dá)式的語法層面的分析, 但是它并不了解這個語義是否真正有含義, 比如 C 語言里面兩個指針做乘法運算是沒有意義的, 但是這個語法卻是合法的.
    編譯器能分析的是靜態(tài)語義, 也就是能在編譯期就確定的語義, 通常包括聲明和類型的匹配, 類型的轉(zhuǎn)換. 與之對應(yīng)的是動態(tài)語義, 就是在運行期才能確定的語義, 比如在運行時將 0 作為除數(shù)是不合法的.
    經(jīng)過語義分析后, 語法樹的表達(dá)式被標(biāo)識了類型 :


    語義分析后的語法樹
  • 代碼優(yōu)化

現(xiàn)在的編譯器有著很多層的優(yōu)化, 往往在源碼級就會有一個優(yōu)化過程, 由源碼級優(yōu)化器完成, 比如 (2+6) 這個表達(dá)式, 在編譯器就可以被確定, 生成如下語法樹 :


優(yōu)化后的語法樹

其實直接在語法樹上做優(yōu)化比較困難, 所以源碼優(yōu)化器往往把整個語法樹轉(zhuǎn)換成中間代碼, 他是語法樹的順序表示, 已經(jīng)非常接近目標(biāo)代碼, 但是他一般跟目標(biāo)機(jī)器和運行時環(huán)境是無關(guān)的, 比如他不包含數(shù)據(jù)的尺寸, 變量地址和寄存器的名字等. 中間代碼使編譯器被分為前端和后端, 編譯器前端負(fù)責(zé)生產(chǎn)與機(jī)器無關(guān)的中間代碼, 編譯器后端將中間代碼轉(zhuǎn)換成目標(biāo)代碼. 這樣對于一些可以跨平臺的編譯器而言, 他們可以針對不同的平臺使用一個前端數(shù)個后端.

  • 目標(biāo)代碼生成

    源代碼優(yōu)化器產(chǎn)生中間代碼后的過程屬于編譯器后端, 主要包括代碼生成器和目標(biāo)代碼優(yōu)化器.
    代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)機(jī)器代碼, 這個過程十分依賴于目標(biāo)機(jī)器, 因為不同的目標(biāo)機(jī)器有著不同的字長, 寄存器, 整數(shù)數(shù)據(jù)類型和浮點數(shù)據(jù)類型等.
    目標(biāo)機(jī)器代碼再由目標(biāo)代碼優(yōu)化器進(jìn)行優(yōu)化, 比如選擇合適的尋址方式, 使用位移來代替運算, 刪除多余指令等.

鏈接

經(jīng)過 掃描 語法分析 語義分析 源代碼優(yōu)化 目標(biāo)代碼生成 目標(biāo)代碼優(yōu)化 這一系列操作, 源碼被編譯成了目標(biāo)代碼, 但是目標(biāo)代碼有一個問題, index 和 array 的地址還沒有確定, 如果 index 和 array 和源代碼在同一個編譯單元, 那么編譯器可以為他們分配空間, 如果定義在別的程序模塊就沒辦法了.

現(xiàn)在程序的代碼規(guī)模往往很大, 所以每個程序會被分為多個模塊, 這樣做的好處是每個模塊之間相互依賴又相互獨立, 而且模塊可以單獨開發(fā)編譯測試, 便于重用. 但是隨之而來的問題就是模塊之間怎么通信, 模塊之間的通信包括函數(shù)的調(diào)用和變量的訪問, 函數(shù)的訪問需要知道函數(shù)的地址, 變量的訪問需要知道變量的地址.

  • 模塊拼裝 --- 靜態(tài)鏈接

    我們把每個源代碼模塊獨立的編譯, 然后將他們組裝起來, 這個組裝的過程就叫鏈接, 連接過程包括了地址分配, 符號決議和重定位.

    模塊間的通信是地址的相互訪問, 解決這個問題的方式就是模塊間符號的引用, 模塊中符號表分為已定義符號集合D,和一個未定義符合集合U, 未定的符號將引用其他模塊中的符號.

    在連接的過程中, 每個模塊會去其他模塊中尋找自己未定義的那些符號的定義, 這個過程就是符號決議.

    在未找到符號符號之前, 模塊先把這個未定義符號的地址置為 0, 當(dāng)在其他模塊中找到了該符號的定義的時候, 會重新給這個符號賦值地址, 這個過程就是重定位.

    靜態(tài)鏈接的進(jìn)本過程和作用 : 比如在程序 main.c 模塊中使用另一個模塊 func.c 中的函數(shù) foo(). 我們再 main.c 模塊中調(diào)用 foo 的時候必須知道 foo 這個函數(shù)的地址, 但是由于每個模塊是單獨編譯的, main.c 編譯的時候并不知道 foo 函數(shù)的地址, 所以他暫時把這個指令擱置(地址置 0), 等到最后連接的時候由連接器將指令的目標(biāo)地址修正, 如果沒有連接器, 我們需要手動修正 foo 的地址, 而且每次編譯后地址可能會改變. 連接器在連接的時候會根據(jù)所引用的符號 foo, 自動取相應(yīng)的 func.c 模塊查找 foo 的地址, 然后將 main.c 模塊中所引用的 foo 指令進(jìn)行重定位,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • CPU的工作分為 5 個階段:取指令階段、指令譯碼階段、執(zhí)行指令階段、訪存取數(shù)和結(jié)果寫回。 1、取指令(IF,in...
    A_MARK閱讀 3,290評論 0 2
  • 由于所在公司前端代碼較不規(guī)范,近期應(yīng)公司領(lǐng)導(dǎo)要求,整理出了一份公司內(nèi)部的前端開發(fā)規(guī)范標(biāo)準(zhǔn)。這里參考了一些文章,并對...
    追尋1989閱讀 1,248評論 0 3
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關(guān)于...
    SeanCST閱讀 8,141評論 0 27
  • # 基礎(chǔ)概念 - 設(shè)計模式六大原則 設(shè)計模式六大原則](http://www.uml.org.cn/sjms/2...
    myr1782閱讀 255評論 0 0
  • 寂靜的夜里,開車回家,又是一個晚歸的夜,腦袋里空空如也,沒有想象力,只盼望早點到家。 想起明天不用上班,心情也是歡...
    吳長燃閱讀 207評論 0 0

友情鏈接更多精彩內(nèi)容