編譯器編譯內(nèi)核原理以及其應(yīng)用 上篇文檔簡(jiǎn)要介紹了一下編譯器的編譯內(nèi)核,當(dāng)然介紹的很簡(jiǎn)單,沒(méi)有深入進(jìn)去,俗話說(shuō)不深入怎么High,所以這里我們深入進(jìn)去搞一下,看看里面都有些什么有用的東東。 這里以市面上用的最多的Keil MDK為例,我們來(lái)研究下。 1、先上一張老圖,然后我們仔細(xì)看下這個(gè)器、那個(gè)器的都是干啥的: 上一篇文章的圖片,上一篇文章也簡(jiǎn)要介紹了下,但你可能會(huì)問(wèn),這東西了解了又能怎樣?有什么用嗎?那么好,司機(jī)我就來(lái)給大家?guī)?,看看了解編譯原理是不是真的那么沒(méi)用? 司機(jī)嘛,首先先來(lái)帶大家認(rèn)認(rèn)路,你說(shuō)這幾個(gè)東東很重要,但是在Keil里面我們?cè)趺凑业剿麄兡兀?br> 你可以在"keil根目錄\ARM\ARMCC\bin"下找到他們,如下圖所示: 就是這幾個(gè)東東,他們就是keil編譯器的內(nèi)核,把你編寫(xiě)的C代碼變成可以燒寫(xiě)到MCU中執(zhí)行的2進(jìn)制文件就是這幾個(gè)東東來(lái)完成的,其中armar.exe就是預(yù)處理器、armcc.exe是編譯器、armasm.exe是匯編器、armlink.exe是鏈接器、fromelf.exe是elf文件的實(shí)用工具集。 有了這幾個(gè)東東你可以甚至都不用Keil的編程界面都可以干活,直接在windows的shell界面里面通過(guò)命令行就可以編譯鏈接,但這個(gè)是真沒(méi)啥用,純裝B,有IDE不用非要自己敲命令行這部找罪受嘛,這里就不介紹這種沒(méi)啥用的招數(shù)了。 當(dāng)然了你也可以在集成開(kāi)發(fā)環(huán)境中見(jiàn)到這幾個(gè)貨: 如上圖: 1、匯編器的相關(guān)選項(xiàng);2、編譯器的相關(guān)選項(xiàng);3、預(yù)處理器的相關(guān)選項(xiàng);4、鏈接器描述文件(*.map)中包含的內(nèi)容; 還有這個(gè): 上圖是專門(mén)的鏈接器相關(guān)配置的界面;那么這些東東都是干嘛用的呢? 讓我們先來(lái)舉個(gè)栗子,考慮比如下面這樣一段代碼:
啟動(dòng)代碼中的Reset_Handler代碼,大家可以一眼看出來(lái)這段代碼是錯(cuò)誤的,其中紅色字體的errorfunc是我故意填在這里的一個(gè)不存在的函數(shù),編譯必然報(bào)錯(cuò),那么我們看看編譯器是怎么報(bào)錯(cuò)的? 重點(diǎn)在途中藍(lán)色底紋的部分,注意第一個(gè)單詞:assembling……,然后后面跟著出錯(cuò)信息,沒(méi)錯(cuò),這是一個(gè)匯編器錯(cuò)誤;再看下面,我再代碼里面給出一個(gè)錯(cuò)誤(我在main.c里面刪掉了一個(gè)頭文件): 還是看藍(lán)色底紋部分,注意第一個(gè)單詞:compiling……,然后后面跟著出錯(cuò)信息,沒(méi)錯(cuò),這是一個(gè)編譯器錯(cuò)誤,跟上面的不同,這是編譯器報(bào)錯(cuò),我們?cè)倏聪旅嬉粋€(gè)錯(cuò)誤,代碼如下:
錯(cuò)誤的是紅色字體的不存在函數(shù)errorfunc,我們make一下整個(gè)工程,看看怎么報(bào)錯(cuò): 還是看藍(lán)色底紋部分,注意第一個(gè)單詞:linking……,然后后面跟著出錯(cuò)信息,這次是一個(gè)鏈接器錯(cuò)誤! 發(fā)現(xiàn)了嗎?大家經(jīng)常說(shuō)的編譯器報(bào)錯(cuò),其實(shí)是幾個(gè)不同的東西再報(bào)錯(cuò),編譯器、匯編器、鏈接器都會(huì)報(bào)錯(cuò),那你可能會(huì)問(wèn),知道這個(gè)有啥用呢?當(dāng)然是有用的,比如匯編器報(bào)錯(cuò),基本跟C語(yǔ)言沒(méi)關(guān)系,基本可以斷定是匯編語(yǔ)言語(yǔ)法錯(cuò)誤或者是嵌入C語(yǔ)言的匯編語(yǔ)言出錯(cuò)(在C中嵌入?yún)R編是一種非常有效的編程手段);如果是鏈接器報(bào)錯(cuò),那就基本跟C語(yǔ)言語(yǔ)法無(wú)關(guān),不是你的C語(yǔ)法上出錯(cuò),很可能是你調(diào)用了不存在的函數(shù)或者鏈接器腳本寫(xiě)錯(cuò)了,或者使用了不存在的標(biāo)號(hào)Symbol;而只有編譯器報(bào)錯(cuò),才總是你的C寫(xiě)的有問(wèn)題,而我見(jiàn)過(guò)很多程序猿,看見(jiàn)報(bào)錯(cuò)就馬上在自己的C代碼里面翻來(lái)覆去的找,結(jié)果根本找不到錯(cuò)誤,所以看到錯(cuò)誤的第一反應(yīng)是看看是誰(shuí)報(bào)錯(cuò)?匯編器、編譯器、還是鏈接器,這樣可以排除掉N多可能性,可以幫助你迅速排查錯(cuò)誤,以上示例代碼很簡(jiǎn)單,大家可以一眼就看到錯(cuò)誤,但是如果你的代碼十分巨大,內(nèi)部代碼調(diào)用關(guān)系十分復(fù)雜的情況下你就會(huì)發(fā)現(xiàn)這個(gè)技巧相當(dāng)有效。 2、你就是老板,當(dāng)然要學(xué)會(huì)看工作報(bào)表: 我就是個(gè)寫(xiě)代碼的,不是BOSS,干好活就得了。但是做開(kāi)發(fā)環(huán)境的可不這么想,對(duì)于IDE來(lái)說(shuō),他們才是干活的,你是告訴他們?cè)趺锤苫畹睦习?,他們干了哪些事情,?huì)通過(guò)報(bào)表的形式匯報(bào)給你,這就是編譯器生成的中間文件,要當(dāng)一個(gè)好老板當(dāng)然得會(huì)看報(bào)表,所以一個(gè)合格的寫(xiě)代碼的要學(xué)會(huì)看編譯器的中間文件。 看下圖……: 這是一個(gè)LPC5408的hello world例程由編譯器生成的中間文件,這些就是你的“預(yù)處理器員工”、“編譯器員工”、“匯編器員工”、“鏈接器員工”的工作報(bào)表。我們來(lái)一起看一下: 首先是擴(kuò)展名為“.i”的預(yù)處理器文件,這貨幾乎很少用到,基本就是把C文件中用到的頭文件內(nèi)容導(dǎo)入到C語(yǔ)言中,如果頭文件部分有問(wèn)題,是比較容易找到問(wèn)題的,很少會(huì)需要“.i”的協(xié)助,這里就不列出來(lái)看了,大家有興趣自己打開(kāi)這種文件看一下。 然后是“*.lst”和“*.txt”編譯器&匯編器的報(bào)表文件,也很少用到,這里略過(guò)。 最后是真正有用的鏈接器描述文件“*.map”非常有用,絕對(duì)是你的“核心員工”的工作報(bào)表,也是最復(fù)雜的一個(gè),工程配置里面可以看到其配置項(xiàng),非常的多,在工程配置的Listing選項(xiàng)卡里面,如下圖: 整個(gè)*.map文件也是巨長(zhǎng),里面大概的內(nèi)容有: (1)標(biāo)號(hào)表 鏈接器生成的所有標(biāo)號(hào),包括所有函數(shù)名&全局變量名稱都可以生成標(biāo)號(hào),每一個(gè)標(biāo)號(hào)的地址以及其占有的存儲(chǔ)空間情況。 (2)存儲(chǔ)空間占用情況 這里只列一部分,map文件里面更詳細(xì),連每一個(gè)函數(shù)的占用情況都會(huì)列出來(lái)。 (3)所有被鏈接(不是編譯)的函數(shù)列表及其地址,沒(méi)有被鏈接的也有,如果你有興趣看的話 如下圖,截取了一小部分
但這些東西具體有什么用,我們會(huì)在以后的文章中大量涉及,這里很難單獨(dú)說(shuō)清楚,先打下一個(gè)基礎(chǔ),我們以后的文章會(huì)經(jīng)常涉及這個(gè)map文件,如果你真的把這個(gè)文件看明白了并能熟練應(yīng)用,那么恭喜你,你就解鎖了一個(gè)重要技能包。 這里對(duì)編譯器部分介紹很簡(jiǎn)單,以后其他文章里面我們會(huì)深入探討,然后我也打算寫(xiě)一篇介紹GCC/GNU工具鏈用于嵌入式開(kāi)發(fā)的文章,那是一片更廣闊的天地。 原本打算寫(xiě)一篇深入介紹編譯器原理&應(yīng)用的文章,但寫(xiě)到這里還是寫(xiě)不下去了,主要是相互依存的點(diǎn)很多,避開(kāi)這些點(diǎn)單獨(dú)談編譯器原理&應(yīng)用是非常不現(xiàn)實(shí)的。想來(lái)想去,這些體系性的東西還是要循序漸進(jìn),以后隨著我們解鎖了更多的技能包我們?cè)贂?huì)深入探討這部分內(nèi)容。 |
|