《電子技術(shù)應(yīng)用》
您所在的位置:首頁(yè) > 可編程邏輯 > 業(yè)界動(dòng)態(tài) > 編譯器的工作過(guò)程

編譯器的工作過(guò)程

2015-09-18
關(guān)鍵詞: 編譯器

  源碼要運(yùn)行,必須先轉(zhuǎn)成二進(jìn)制的機(jī)器碼,。這是編譯器的任務(wù),。
  比如,下面這段源碼(假定文件名叫做test.c),。
  #include <stdio.h>
  int main(void)
  {
  fputs("Hello, world!\n", stdout);
  return 0;
  }
  要先用編譯器處理一下,,才能運(yùn)行。
  $ gcc test.c
  $ ./a.out
  Hello, world!
  對(duì)于復(fù)雜的項(xiàng)目,,編譯過(guò)程還必須分成三步,。
  $ ./configure
  $ make
  $ make install
  這些命令到底在干什么?大多數(shù)的書籍和資料,,都語(yǔ)焉不詳,,只說(shuō)這樣就可以編譯了,沒(méi)有進(jìn)一步的解釋,。
  本文將介紹編譯器的工作過(guò)程,,也就是上面這三個(gè)命令各自的任務(wù),。我主要參考了Alex Smith的文章《Building C Projects》。需要聲明的是,,本文主要針對(duì)gcc編譯器,,也就是針對(duì)C和C++,不一定適用于其他語(yǔ)言的編譯,。
  第一步 配置(configure)
  編譯器在開(kāi)始工作之前,,需要知道當(dāng)前的系統(tǒng)環(huán)境,比如標(biāo)準(zhǔn)庫(kù)在哪里,、軟件的安裝位置在哪里,、需要安裝哪些組件等等。這是因?yàn)椴煌?jì)算機(jī)的系統(tǒng)環(huán)境不一樣,,通過(guò)指定編譯參數(shù),,編譯器就可以靈活適應(yīng)環(huán)境,編譯出各種環(huán)境都能運(yùn)行的機(jī)器碼,。這個(gè)確定編譯參數(shù)的步驟,,就叫做"配置"(configure)。
  這些配置信息保存在一個(gè)配置文件之中,,約定俗成是一個(gè)叫做configure的腳本文件,。通常它是由autoconf工具生成的。編譯器通過(guò)運(yùn)行這個(gè)腳本,,獲知編譯參數(shù),。
  configure腳本已經(jīng)盡量考慮到不同系統(tǒng)的差異,并且對(duì)各種編譯參數(shù)給出了默認(rèn)值,。如果用戶的系統(tǒng)環(huán)境比較特別,,或者有一些特定的需求,就需要手動(dòng)向configure腳本提供編譯參數(shù),。
  $ ./configure --prefix=/www --with-mysql
  上面代碼是php源碼的一種編譯配置,,用戶指定安裝后的文件保存在www目錄,并且編譯時(shí)加入mysql模塊的支持,。
  第二步 確定標(biāo)準(zhǔn)庫(kù)和頭文件的位置
  源碼肯定會(huì)用到標(biāo)準(zhǔn)庫(kù)函數(shù)(standard library)和頭文件(header),。它們可以存放在系統(tǒng)的任意目錄中,編譯器實(shí)際上沒(méi)辦法自動(dòng)檢測(cè)它們的位置,,只有通過(guò)配置文件才能知道,。
  編譯的第二步,就是從配置文件中知道標(biāo)準(zhǔn)庫(kù)和頭文件的位置,。一般來(lái)說(shuō),,配置文件會(huì)給出一個(gè)清單,列出幾個(gè)具體的目錄,。等到編譯時(shí),,編譯器就按順序到這幾個(gè)目錄中,,尋找目標(biāo)。
  第三步 確定依賴關(guān)系
  對(duì)于大型項(xiàng)目來(lái)說(shuō),,源碼文件之間往往存在依賴關(guān)系,,編譯器需要確定編譯的先后順序。假定A文件依賴于B文件,,編譯器應(yīng)該保證做到下面兩點(diǎn),。
  (1)只有在B文件編譯完成后,,才開(kāi)始編譯A文件,。
  (2)當(dāng)B文件發(fā)生變化時(shí),,A文件會(huì)被重新編譯,。
  編譯順序保存在一個(gè)叫做makefile的文件中,里面列出哪個(gè)文件先編譯,,哪個(gè)文件后編譯,。而makefile文件由configure腳本運(yùn)行生成,這就是為什么編譯時(shí)configure必須首先運(yùn)行的原因,。
  在確定依賴關(guān)系的同時(shí),,編譯器也確定了,編譯時(shí)會(huì)用到哪些頭文件,。
  第四步 頭文件的預(yù)編譯(precompilation)
  不同的源碼文件,,可能引用同一個(gè)頭文件(比如stdio.h)。編譯的時(shí)候,,頭文件也必須一起編譯,。為了節(jié)省時(shí)間,編譯器會(huì)在編譯源碼之前,,先編譯頭文件,。這保證了頭文件只需編譯一次,,不必每次用到的時(shí)候,,都重新編譯了。
  不過(guò),,并不是頭文件的所有內(nèi)容,,都會(huì)被預(yù)編譯。用來(lái)聲明宏的#define命令,,就不會(huì)被預(yù)編譯,。
  第五步 預(yù)處理(Preprocessing)
  預(yù)編譯完成后,編譯器就開(kāi)始替換掉源碼中bash的頭文件和宏,。以本文開(kāi)頭的那段源碼為例,,它包含頭文件stdio.h,,替換后的樣子如下。
  extern int fputs(const char *, FILE *);
  extern FILE *stdout;
  int main(void)
  {
  fputs("Hello, world!\n", stdout);
  return 0;
  }
  為了便于閱讀,,上面代碼只截取了頭文件中與源碼相關(guān)的那部分,,即fputs和FILE的聲明,省略了stdio.h的其他部分(因?yàn)樗鼈兎浅iL(zhǎng)),。另外,,上面代碼的頭文件沒(méi)有經(jīng)過(guò)預(yù)編譯,而實(shí)際上,,插入源碼的是預(yù)編譯后的結(jié)果,。編譯器在這一步還會(huì)移除注釋。
  這一步稱為"預(yù)處理"(Preprocessing),,因?yàn)橥瓿芍?,就要開(kāi)始真正的處理了。
  第六步 編譯(Compilation)
  預(yù)處理之后,,編譯器就開(kāi)始生成機(jī)器碼,。對(duì)于某些編譯器來(lái)說(shuō),還存在一個(gè)中間步驟,,會(huì)先把源碼轉(zhuǎn)為匯編碼(assembly),,然后再把匯編碼轉(zhuǎn)為機(jī)器碼。
  下面是本文開(kāi)頭的那段源碼轉(zhuǎn)成的匯編碼,。
  .file   "test.c"
  .section    .rodata
  .LC0:
  .string "Hello, world!\n"
  .text
  .globl  main
  .type   main, @function
  main:
  .LFB0:
  .cfi_startproc
  pushq   %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq    %rsp, %rbp
  .cfi_def_cfa_register 6
  movq    stdout(%rip), %rax
  movq    %rax, %rcx
  movl    $14, %edx
  movl    $1, %esi
  movl    $.LC0, %edi
  call    fwrite
  movl    $0, %eax
  popq    %rbp
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
  .LFE0:
  .size   main, .-main
  .ident  "GCC: (Debian 4.9.1-19) 4.9.1"
  .section    .note.GNU-stack,"",@progbits
  這種轉(zhuǎn)碼后的文件稱為對(duì)象文件(object file),。
  第七步 連接(Linking)
  對(duì)象文件還不能運(yùn)行,必須進(jìn)一步轉(zhuǎn)成可執(zhí)行文件,。如果你仔細(xì)看上一步的轉(zhuǎn)碼結(jié)果,,會(huì)發(fā)現(xiàn)其中引用了stdout函數(shù)和fwrite函數(shù)。也就是說(shuō),,程序要正常運(yùn)行,,除了上面的代碼以外,還必須有stdout和fwrite這兩個(gè)函數(shù)的代碼,,它們是由C語(yǔ)言的標(biāo)準(zhǔn)庫(kù)提供的,。
  編譯器的下一步工作,就是把外部函數(shù)的代碼(通常是后綴名為.lib和.a的文件),,添加到可執(zhí)行文件中,。這就叫做連接(linking)。這種通過(guò)拷貝,,將外部函數(shù)庫(kù)添加到可執(zhí)行文件的方式,,叫做靜態(tài)連接(static linking),后文會(huì)提到還有動(dòng)態(tài)連接(dynamic linking)。
  make命令的作用,,就是從第四步頭文件預(yù)編譯開(kāi)始,,一直到做完這一步。
  第八步 安裝(Installation)
  上一步的連接是在內(nèi)存中進(jìn)行的,,即編譯器在內(nèi)存中生成了可執(zhí)行文件,。下一步,必須將可執(zhí)行文件保存到用戶事先指定的安裝目錄,。
  表面上,,這一步很簡(jiǎn)單,就是將可執(zhí)行文件(連帶相關(guān)的數(shù)據(jù)文件)拷貝過(guò)去就行了,。但是實(shí)際上,,這一步還必須完成創(chuàng)建目錄、保存文件,、設(shè)置權(quán)限等步驟,。這整個(gè)的保存過(guò)程就稱為"安裝"(Installation)。
  第九步 操作系統(tǒng)連接
  可執(zhí)行文件安裝后,,必須以某種方式通知操作系統(tǒng),,讓其知道可以使用這個(gè)程序了。比如,,我們安裝了一個(gè)文本閱讀程序,,往往希望雙擊txt文件,該程序就會(huì)自動(dòng)運(yùn)行,。
  這就要求在操作系統(tǒng)中,,登記這個(gè)程序的元數(shù)據(jù):文件名、文件描述,、關(guān)聯(lián)后綴名等等,。Linux系統(tǒng)中,這些信息通常保存在/usr/share/applications目錄下的.desktop文件中,。另外,,在Windows操作系統(tǒng)中,還需要在Start啟動(dòng)菜單中,,建立一個(gè)快捷方式,。
  這些事情就叫做"操作系統(tǒng)連接"。make install命令,,就用來(lái)完成"安裝"和"操作系統(tǒng)連接"這兩步,。
  第十步 生成安裝包
  寫到這里,,源碼編譯的整個(gè)過(guò)程就基本完成了,。但是只有很少一部分用戶,愿意耐著性子,,從頭到尾做一遍這個(gè)過(guò)程,。事實(shí)上,,如果你只有源碼可以交給用戶,他們會(huì)認(rèn)定你是一個(gè)不友好的家伙,。大部分用戶要的是一個(gè)二進(jìn)制的可執(zhí)行程序,,立刻就能運(yùn)行。這就要求開(kāi)發(fā)者,,將上一步生成的可執(zhí)行文件,,做成可以分發(fā)的安裝包。
  所以,,編譯器還必須有生成安裝包的功能,。通常是將可執(zhí)行文件(連帶相關(guān)的數(shù)據(jù)文件),以某種目錄結(jié)構(gòu),,保存成壓縮文件包,,交給用戶。
  第十一步 動(dòng)態(tài)連接(Dynamic linking)
  正常情況下,,到這一步,,程序已經(jīng)可以運(yùn)行了。至于運(yùn)行期間(runtime)發(fā)生的事情,,與編譯器一概無(wú)關(guān),。但是,開(kāi)發(fā)者可以在編譯階段選擇可執(zhí)行文件連接外部函數(shù)庫(kù)的方式,,到底是靜態(tài)連接(編譯時(shí)連接),,還是動(dòng)態(tài)連接(運(yùn)行時(shí)連接)。所以,,最后還要提一下,,什么叫做動(dòng)態(tài)連接。
  前面已經(jīng)說(shuō)過(guò),,靜態(tài)連接就是把外部函數(shù)庫(kù),,拷貝到可執(zhí)行文件中。這樣做的好處是,,適用范圍比較廣,,不用擔(dān)心用戶機(jī)器缺少某個(gè)庫(kù)文件;缺點(diǎn)是安裝包會(huì)比較大,,而且多個(gè)應(yīng)用程序之間,,無(wú)法共享庫(kù)文件。動(dòng)態(tài)連接的做法正好相反,,外部函數(shù)庫(kù)不進(jìn)入安裝包,,只在運(yùn)行時(shí)動(dòng)態(tài)引用。好處是安裝包會(huì)比較小,多個(gè)應(yīng)用程序可以共享庫(kù)文件,;缺點(diǎn)是用戶必須事先安裝好庫(kù)文件,,而且版本和安裝位置都必須符合要求,否則就不能正常運(yùn)行,。
  現(xiàn)實(shí)中,,大部分軟件采用動(dòng)態(tài)連接,共享庫(kù)文件,。這種動(dòng)態(tài)共享的庫(kù)文件,,Linux平臺(tái)是后綴名為.so的文件,Windows平臺(tái)是.dll文件,,Mac平臺(tái)是.dylib文件,。

本站內(nèi)容除特別聲明的原創(chuàng)文章之外,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,,并不代表本網(wǎng)站贊同其觀點(diǎn),。轉(zhuǎn)載的所有的文章、圖片,、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有,。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無(wú)法一一聯(lián)系確認(rèn)版權(quán)者。如涉及作品內(nèi)容,、版權(quán)和其它問(wèn)題,,請(qǐng)及時(shí)通過(guò)電子郵件或電話通知我們,以便迅速采取適當(dāng)措施,,避免給雙方造成不必要的經(jīng)濟(jì)損失,。聯(lián)系電話:010-82306118;郵箱:[email protected],。