項目開發小結之——編譯、鏈接


  距離上次寫博文又有差不多兩個多月了,想了一下,鑒於柯南快完結了,頭像必須換成柯南的。這次開發是需要在x86下搭建一個交叉編譯開發環境,簡而言之,就是在windows平台上開發運行於其他平台的程序(項目內容還是要保密的~~)。在某賓館封閉開發兩個月左右,雖然不是從一開始就和整個團隊在一起做,但我的工作量還是挺大的,而且也學到了不少東西,還是用程序員自己表達方式來抒發一下感受:

1 while(StayAlive)
2 {
3  keepLearning();      
4  stayCoding();  
5  rethinkAndSummarize();              
6 }

  不總結就不能提高,打算寫三篇左右來做個小結。項目中涉及的內容特別多,主要是三條主線:

  1.程序構建過程;

  2.eclipse插件開發與CDT項目;

  3.關於項目開發流程的問題。

  這篇就主要寫我對程序構建過程的一些理解吧,也歡迎大家補充斧正。

一、程序構建流程

  程序構建過程的確是博大精深,就算接觸了一些,也只能淺淺地談談。編譯器的種類眾多,但原理上都是大同小異,以linux下的gcc為例,程序構建流程主要分為四步:

預處理→編譯→匯編→鏈接

預處理:完成宏定義的替換,去掉所有注釋信息及條件編譯信息,形式上是由.c文件生成.i文件,想查看這個過程,可以用-E參數來生成.i文件。這個時候,包含的頭文件和宏定義都會展開。為查看這個過程,用以下代碼測試,環境是CentOS下gcc編譯器。使用命令:

$gcc -E hello.c -o hello.i

將結果導出到文件中查看,可以看到前面的#include<stdio.h>已經被替換成頭文件了(由於太長,我就只截取了最后一部分),使得行數達到了860行,而且我們自己定義的#define MAX 100也已經被替換成了100這個數值了:)

編譯:形式上就是.c文件變成.S文件的過程。經過詞法分析、語法分析、語義分析,將高級語言代碼轉換成匯編語言指令。現在的gcc都是把預處理和編譯兩個步驟合成一個了,由后台的cc1程序完成這些過程處理,實際上gcc也就是一系列后台程序包裝而成的。

$gcc -S hello.i -o hello.S

來看看我們生成的匯編代碼,首先是前面將"hello world"字符串放在.LC0處,之后將寄存器ebp的值入棧,用ebp保存esp的值,將棧頂指針減32(相當於開辟空間供局部變量使用),將值100賦給地址為28的空間(4個字節),再將LC0的地址入棧,之后調用puts輸出信息,接着nop延遲,leave指令恢復之前的ebp和esp,ret返回。

匯編:匯編器as將匯編代碼轉化為機器可以執行的機器指令,形式上是將.S文件轉換為.o的目標文件。

$gcc -c hello.s -o hello.o

$objdump -h hello.o

查看.o目標文件的內容,可以看到代碼段.text大小為32個字節,數據段為0,只讀數據段大小為12字節(就是字符串的大小),.bss段大小也為0

 

$readelf -h hello.o查看ELF文件內容:

鏈接:.o文件還不能夠被計算機所執行,必須轉換為可執行的二進制文件才能被計算機裝載到內存中執行。而.o文件轉換為可執行二進制文件的過程就叫做鏈接。可執行二進制文件的形式有很多(比如Windows下的.exe,一般的.ELF文件等),這個過程中,由於各個.o文件中所需要的符號定義可能在其他.o文件中,所以要將這些.o文件一起合並起來,同時還需要和一些庫文件(比如.a文件)鏈接在一起,這樣才能組合成可執行二進制文件,這個過程由連接器ld控制完成。 

如要要讓這個hello程序能夠執行,則需要鏈接進libc.a,因為printf的符號定義在printf.o里面,而printf.o里又需要其他符號定義,所以必須將需要的所有.a和.o文件都鏈接進去才能生成一個可執行的二進制文件,這里我就沒列出來了。                                            

  鏈接中又有靜態鏈接動態鏈接的概念。鏈接的過程需要知道鏈接腳本作用,靜態鏈接庫和動態鏈接庫,符號表是怎么生成,做什么用的。.o文件如何變成elf文件。

  動態鏈接庫:多個應用程序可以使用同一個動態庫,啟動多個應用程序的時候,只需要將動態庫加載到內存一次即可。動態鏈接庫的編譯方法:gcc -shared -fPIC -o libhello.so hello.c hello1.c,這樣就會在當前目錄下生成一個libhello.so文件。

  靜態鏈接庫:代碼的裝載速度快,執行速度也比較快,因為編譯時它只會把你需要的那部分鏈接進去,應用程序相對比較大。但是如果多個應用程序使用的話,會被裝載多次,浪費內存。我們的項目中,由於是嵌入式平台(資源相對較少,程序一般固化在硬件平台上),所以編譯出來的可執行文件實際上是靜態鏈接了很多系統庫文件,一個helloworld的程序就有2.76M(當然也附帶了一些調試信息)。
  靜態鏈接庫的編譯方法:先用gcc -c hello.c hello1.c得到hello.o、hello1.o文件,然后再將.o文件打包成.a文件,即ar -r libhello.a hello.o hello1.o。在當前目錄下就生成了libhello.a文件。

  可執行程序的格式:ELF等,一般主要分為三段:.text、.data、.bss(圖中.rdata為只讀段)。其中.text段存放程序代碼,已初始化的全局變量和靜態變量存放在.data段中、未初始化的全局變量和靜態變量存放在.bss段中,而.rdata段存放只讀的變量。

  

二、相關編譯選項

  編譯過程需要知道各種編譯選項的作用,我只寫了我在項目中碰見的一些編譯選項(交叉編譯的目標機是mips架構的)。項目里要求開發一個IDE,所以必須要搞清楚其中涉及到的所有編譯選項作用,並且將其加入基於eclipse框架的IDE(就是eclipse插件開發相關的東西,擴展工程屬性中的編譯選項和路徑指定)中去,當時在這里也是花了相當長時間研究怎么生成一個正確的可執行文件(凡是搞過交叉開發的都懂的~~)。

-c:只編譯不鏈接

-o:指定目標輸出文件

-M:明確依賴關系

-E: 只進行預處理

-g:編譯時生成調試信息(g1/g2/g3)

-mips3:指定架構

-EL:指定小端對齊模式

-fomit-frame-pointer:采用

-O0/O1/O2/O3:優化選項

-I:指定頭文件包含路徑

-L:指定鏈接庫查找路徑

-specs:指定使用的bsp文件的名稱

-Xlinker:用於將選項或參數傳遞給連接器

在eclipse CDT插件中增加編譯選項的option。(由於項目要求,就把關鍵詞打碼了,見諒~)

  程序的構建過程由Makefile來控制,linux下構建一個由多個文件組成的程序一般都用Makefile來完成,將所有的編譯命令按Makefile的格式寫入Makefile就可以大大提高程序的構建效率,避免重復地輸入編譯命令。只需要在對應目錄下輸入make命令就可以調用make工具自動完成對Makefile的解析並執行Makefile中的命令。其實大家常用的VS構建程序時也是用了Makefile的,只不過VS下構建的Makefile格式和linux下不一樣,而且VS自動幫你寫了Makefile而且沒在項目中生成,所以就找不到Makefile文件了。

  做了這個項目才知道Makefile還分PosixMakefile和GNUMakefile,不過有哪些區別暫時還沒搞清楚。Makefile生成過程,分為Makefile,subDir.mk,Objects.mk,sources.mk。先生成Makefille文件,再向Makefile文件中寫入各種包含的Makefile文件,之后寫入編譯命令和文件依賴規則等。

  說到這里就不得不吐槽一下,在我之前接收這個項目的兩個開發人員由於完全沒有面向對象的思想,也沒有抱着認真負責的態度來開發,基本上是以調試方式在開發,見到出問題的地方就隨便加、隨便改代碼,運氣好出來正確效果了就行了,運氣不好就不知道怎么辦了,各種Dead Code,完全沒有弄懂為什么別人的代碼要這么寫,是什么原理;更糟的是沒有代碼更改記錄,僅有的注釋也就是自己的名字和某些被注掉的代碼,使得我花了大把的時間去定位他們修改過的位置(真是給我挖了一個大坑!),然后再挨個改回來。一個.java文件多的就有3000~5000行代碼,一個package里一般有十幾個.java文件,而一個插件里面又有十幾package,總共有幾十個插件。當然,我想這應該也不是個別現象,中國做開發的好多企業或者單位肯定都有這種通病。反觀國外的做法,拿eclipse和CDT說吧,一個開源項目那么多人都動過,IBM、Wind River、甚至很多貢獻過的程序員的注釋都十分規范,易讀,代碼整潔干凈,命名清晰,起碼能夠給二次開發的人員相當大的支持。

  先寫這么多吧~搞了兩個月的項目,幾乎都沒什么時間認真總結、也沒時間看書,東西很多很混亂,還是要多多學習總結,下一篇准備寫eclipse 和 CDT的一些介紹和開發總結。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM