Linux | 编译原理、gcc的命令参数、自动化构建工具 make/Makefile
文章目錄
- 編譯原理
- 預處理
- 編譯
- 匯編
- 鏈接
- gcc的常用命令參數(shù)
- make 和 Makefile 的概念
- make的運行
- 通配符
- 自動化變量
- 偽目標.PHONE:【命令】
編譯原理
在解釋 makefile 前,首先解釋一下 .c 文件變成 .exe 文件要經(jīng)過的四個步驟——預處理、編譯、匯編和鏈接(參考來源):
windows 系統(tǒng)下最后生成的可執(zhí)行文件為 .exe ,但 Linux 系統(tǒng)下為 .out 。此處的可執(zhí)行文件僅針對一般 .c/.cpp 代碼而言。
預處理
預處理分為四步:
- 展開所有的宏定義 #define
- 處理含有 # 部分的代碼。如:
- 條件編譯 “#if”、“#ifdef”、“#elif”、“#else”、“#endif” ;
- 預編譯指令 #include ,將被包含的頭文件插入到該編譯指令的位置。(這個過程是遞歸進行的,因為被包含的文件可能還包含了其他文件)
- 刪除所有的注釋 “//” 和 “/* */” 。
- 添加行號和文件名標識,方便后續(xù)編譯時 編譯器產(chǎn)生調(diào)試用的行號 以及 在產(chǎn)生編譯錯誤或警告時能夠顯示行號。
- 保留所有的 #pragma 編譯指令,因為編譯器需要使用它們。
編譯
編譯過程是整個程序構(gòu)建的核心部分,編譯成功,會將源代碼由 文本形式轉(zhuǎn)換成機器語言 ,編譯過程就是把預處理完的文件進行一系列 詞法分析、語法分析、語義分析以及優(yōu)化后生成相應的匯編代碼文件(.s)。
-
詞法分析: 使用一種叫做 lex 的程序?qū)崿F(xiàn)詞法掃描,它會按照用戶之前描述好的詞法規(guī)則將輸入的字符串分割成一個個記號。產(chǎn)生的記號一般分為:關鍵字、標識符、字面量(包含數(shù)字、字符串等)和特殊符號(運算符、等號等),然后他們放到對應的表中。
-
語法分析: 語法分析器根據(jù)用戶給定的語法規(guī)則,將詞法分析產(chǎn)生的記號序列進行解析,然后將它們構(gòu)成一棵語法樹。對于不同的語言,只是其語法規(guī)則不一樣。用于語法分析也有一個現(xiàn)成的工具,叫做:yacc。
-
語義分析: 語法分析完成了對表達式語法層面的分析,但是它不了解這個語句是否真正有意義。有的語句在語法上是合法的,但是卻是沒有實際的意義,比如說兩個指針的做乘法運算,這個時候就需要進行語義分析,但是編譯器能分析的語義也只有靜態(tài)語義。
- 靜態(tài)語義:在編譯期就可以確定的語義。 通常包括聲明與類型的匹配、類型的轉(zhuǎn)換。比如當一個浮點型的表達式賦值給一個整型的表達式時,其中隱含一個從浮點型到整型的轉(zhuǎn)換,而語義分析就需要完成這個轉(zhuǎn)換,而將一個浮點型的表達式賦值給一個指針,這肯定是不行的,語義分析的時候就會發(fā)現(xiàn)兩者類型不匹配,編譯器就會報錯。
- 動態(tài)語義:只有在運行期才能確定的語義。 比如說兩個整數(shù)做除法,語法上沒問題,類型也匹配,聽著好像沒毛病,但是,如果除數(shù)是0的話,這就有問題了,而這個問題事先是不知道的,只有在運行的時候才能發(fā)現(xiàn)他是有問題的,這就是動態(tài)語義。
-
中間代碼生成: 初始代碼是可以進行優(yōu)化的,對于一些在編譯期間就能確定的值,可以直接直接進行處理,比如說 2+6,在編譯期間就可以確定他的值為8了,但是直接在語法上進行優(yōu)化的話比較困難,這時優(yōu)化器會先將語法樹轉(zhuǎn)成中間代碼。中間代碼一般與目標機器和運行環(huán)境無關。(不包含數(shù)據(jù)的尺寸、變量地址和寄存器的名字等)。中間代碼在不同的編譯器中有著不同的形式,比較常見的有三地址碼和P-代碼。
中間代碼使得編譯器可以分為前端和后端。編譯器前端負責產(chǎn)生于機器無關的中間代碼,編譯器后端將中間代碼換成機器代碼。 -
目標代碼生成與優(yōu)化: 代碼生成器將中間代碼轉(zhuǎn)成機器代碼,這個過程是依賴于目標機器的,因為不同的機器有著不同的字長、寄存器、數(shù)據(jù)類型等。
最后目標代碼優(yōu)化器對目標代碼進行優(yōu)化,比如選擇合適的尋址方式、使用唯一來代替乘除法、刪除出多余的指令等。
匯編
匯編過程調(diào)用 匯編器 as 來完成,將匯編代碼轉(zhuǎn)換成機器可以執(zhí)行的指令,每一個匯編語句幾乎都對應一條機器指令。
使用命令 as hello.s -o hello.o 或者使用 gcc -c hello.s -o hello.o 來執(zhí)行匯編,對應生成的文件是 .o 文件。
鏈接
鏈接的主要內(nèi)容就是將各個模塊之間相互引用的部分正確的銜接起來。它的工作就是把一些指令對其他符號地址的引用加以修正。
鏈接過程主要包括了地址和空間分配、符號決議和重定向:
-
符號決議: 有時候也被叫做符號綁定、名稱綁定、名稱決議、或者地址綁定,其實就是指用符號來去標識一個地址。
比如說 int a = 6;這樣一句代碼,用a來標識一個塊4個字節(jié)大小的空間,空間里邊存放的內(nèi)容就是4. -
重定位: 重新計算各個目標的地址過程叫做重定位。
鏈接有兩種模式:
-
靜態(tài)鏈接: 程序運行前,將每個模塊的源代碼文件編譯成目標文件(Linux:.o Windows:.obj),然后將 目標文件 和 庫 一起鏈接形成最后的可執(zhí)行文件。
庫其實就是一組目標文件的包,就是一些最常用的代碼變異成目標文件后打包存放。最常見的庫就是運行時庫,它是支持程序運行的基本函數(shù)的集合。
-
動態(tài)鏈接: 程序運行期間,系統(tǒng)調(diào)用動態(tài)鏈接器(ld-linux.so)自動鏈接的過程。
舉例描述:
- 靜態(tài)鏈接: 如果鏈接到可執(zhí)行文件中的是 靜態(tài)連接庫 libmyprintf.a ,那么 虛擬內(nèi)存代碼段中的 .rodata 節(jié)區(qū) 在鏈接后需要被重定位到一個絕對的虛擬內(nèi)存地址,以便程序運行時能夠正確訪問該節(jié)區(qū)中的字符串信息。
- 動態(tài)鏈接: 而對于puts,因為它是動態(tài)連接庫 libc.so 中定義的函數(shù),所以會在程序運行時通過 動態(tài)符號鏈接 找出 puts 函數(shù) 在 內(nèi)存 中的地址,以便程序調(diào)用該函數(shù)。
gcc的常用命令參數(shù)
上面提到的四個步驟可以由 編程語言譯器 gcc 來完成,gcc軟件 通過 gcc這條命令 來實現(xiàn)各種功能,下面來看一下 gcc命令 的常用選項:
-Idir :當你使用 #include”file” 的時候:
如果使用 -I 指定了目錄,gcc/g++ 會先在指定的目錄查找;否則,在當前目錄查找指定的頭文件。
如果沒有找到,回到默認的頭文件目錄查找。
-idirafter dir :在 -I 的目錄里面查找失敗,則到這個目錄里面查找。
-llibrary :定制編譯的時候使用的庫。
-Ldir :指定編譯的時候搜索庫的路徑。如果有自己的庫,可以用它來定制搜索目錄,否則編譯器只在標準庫目錄里面找。dir 是目錄的名字。
-M :生成文件關聯(lián)信息。包含目標文件所依賴的所有源代碼。
make 和 Makefile 的概念
推薦一個非常全的關于 Makefile 的文章:跟我一起學寫 Makefile
在我們?nèi)粘懘a中,一個工程的源文件不計其數(shù), 按照類型、功能、模塊等分別放在若干個目錄中,這時候我們就可以利用 Makefile 來指定哪些文件先編譯,哪些后編譯,以及更復雜的操作。
make 是一個命令工具,它解釋 Makefile 中的指令。我們只需要在 Makefile 里指定所有的操作,再用 make 這個操作,即可讓整個工程自動編譯。
makefile 的格式如下:
target : prerequisitescommand- target: 目標文件 ,可以是多個文件,以空格分開,可以使用通配符。可以是 Object File 或 執(zhí)行文件 。甚至還可以是一個 標簽(Label),如:clean 。
- prerequisites: target 的 依賴對象 。如果其中的 某個文件 要比 目標文件 要新,那么,目標文件 就被認為是 過時的 ,需要重新生成。
- command: 命令行 ,如果其不與 target:prerequisites 在一行,那么,必須以 [Tab鍵] 開頭,如果在一行,那么可以用分號做為分隔。
一般來說,make會以UNIX的標準Shell,也就是/bin/sh來執(zhí)行命令。
寫一個 makefile 文件為例:
目標程序:
執(zhí)行 make 指令:
這樣就生成了 .i, 、.s 、.o 、.out 文件。那么 make 是怎么運行的呢?
make的運行
也可以給 make 命令 指定一個 特殊名字 的 Makefile 。這需要使用 make 的 -f 或是 --file 參數(shù)( --makefile 參數(shù)也行)。例如,我們有個 Makefile 的名字是 hchen.mk ,則可以這樣執(zhí)行 make 命令:
make –f hchen.mk如果在 make 的命令行中,不只一次地使用了 -f 參數(shù),那么,所有指定的 Makefile 將會被連在一起傳遞給 make 執(zhí)行。
這就是整個 make 的運行過程,make 會一層又一層地去找文件的依賴關系,直到:
- 最終編譯出第一個目標文件(默認目標)并返回退出碼;
- 或者因為缺少必要規(guī)則而直接返回退出碼。
make命令執(zhí)行后有三個退出碼:
- 0 :表示成功執(zhí)行。
- 1 :如果 make 運行時出現(xiàn)任何錯誤,返回 1 。
- 2 :如果你使用了 make 的 -q 參數(shù),導致一些目標不需要更新,那么返回 2 。
而對于所定義的命令的錯誤,或是編譯不成功,make根本不理。
通配符
可以通過通配符來簡化命令行:
- ~ :Unix下, ~/test 表示當前用戶的 $HOME 目錄下的 test 目錄。而 ~hchen/test 則表示用戶 hchen 的宿主目錄下的 test 目錄。而在 Windows 或是 MS-DOS下 ,用戶沒有宿主目錄 ,那么波浪號所指的目錄則根據(jù)環(huán)境變量 HOME 而定。(make支持UNIX下的通配符用法)
- * :表示任意長度的字符串,*.c 表示所有后綴為c的文件。而當文件名中有通配符,如: ~ ,那么可以用轉(zhuǎn)義字符 \ ,如 \~ 來表示真實的 ~ 字符。
- ? :表示任意一個字符串。
自動化變量
shell 中的 自動化變量(又名:特殊變量) ,make 也是支持的,經(jīng)常用到下面前三個自動化變量 :
- $@ :目標對象 。在模式規(guī)則中,如果有多個目標,那么, $@ 就是匹配于目標中模式定義的集合。
- $^ :所有 依賴對象 ,以空格分隔。如果在依賴目標中有多個重復的,那么這個變量會去除重復的依賴目標,只保留一份。
- $< :所有 依賴對象 的 第一個 。如果依賴目標是以 模式(即 % )定義的,那么 $< 將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
- $? :所有比 目標對象 新的 依賴對象 的集合。以空格分隔。
- $+ : 這個變量很像 $^ ,也是所有 依賴對象 的集合。只是它不去重。
- $% :僅當 目標對象 是函數(shù)庫文件中、表示規(guī)則中的目標成員名。例如,如果一個目標是 foo.a(bar.o) ,那么, $% 就是 bar.o , $@ 就是 foo.a 。如果目標不是函數(shù)庫文件(Unix下是 .a ,Windows下是 .lib ),那么,其值為空。
- $* :這個變量表示目標模式中 % 及其之前的部分。(如果 目標對象 是 dir/a.foo.b ,并且 目標對象 的 模式 是 a.%.b ,那么, $* 的值就是 dir/a.foo 。)
- 這個變量對于構(gòu)造有關聯(lián)的文件名是比較有用的。(如果 目標對象 中沒有 模式 的定義,那么 $* 也就不能被推導出,但是,如果 目標文件 的后綴是 make 所識別的,那么 $* 就是除了后綴的那一部分。)
例如:如果 目標對象 是 foo.c ,因為 .c 是 make 所能識別的后綴名,所以, $* 的值就是 foo 。這個特性是 GNU make 的,很有可能不兼容于其它版本的 make ,所以,盡量避免使用 $* ,除非是在 隱含規(guī)則 或是 靜態(tài)模式 中。如果 目標對象 中的后綴是 make 所不能識別的,那么 $* 就是空值。
我們可以利用 自動化變量 簡化 makefile 文件:
執(zhí)行 make 命令:
我們還能進一步再簡化,可以利用通配符來表示,在多個 目標對象 的 依賴對象 和 命令行 都相似時,利用通配符 % 來減少工作量,這樣就可以不用一個個寫出每個文件的生成規(guī)則了。
偽目標.PHONE:【命令】
.PHONE: [命令] // 聲明偽目標,無論目標是否最新,每次都重新生成。舉個 偽目標 的例子:
clean:rm *.o temp既然我們生成了許多編譯文件,那么我們也應該提供一個清除它們的 目標 以備完整地重編譯。 (以“make clean”來使用該目標)
之所以將 clean 稱為 偽目標 , 是因為我們并不生成 clean 這個文件。偽目標 并不是一個 文件 ,只是一個 標簽 ,由于 偽目標 不是 文件 ,所以 make 無法生成它的 依賴對象 ,無法決定它是否要執(zhí)行 命令行 。我們只有顯式地指明這個 目標 才能讓其生效。當然,偽目標 的取名不能和 文件名 重名,不然其就失去了 偽目標 的意義了。
因此我們需要用 .PHONY 聲明 偽目標 ,從而區(qū)分 偽目標 和 目標文件 。
.PHONY : clean而只要有 .PHONY:clean 這個聲明,不管是否有 clean 文件,只要執(zhí)行 make clean 命令,就會運行 clean 。因此,我們要在聲明后面跟上 clean 的具體內(nèi)容:
.PHONY : clean clean :rm *.o temp通常需要生成的程序不會設置偽對象,因為每個項目的構(gòu)建需要很長的時間,所以盡可能判斷不需要生成就不用重新生成。
偽目標一般沒有依賴的文件。但是,我們也可以為偽目標指定所依賴的文件。
一個示例就是,如果你的 Makefile 需要一口氣生成若干個可執(zhí)行文件,但你只想簡單地敲一個 make 完事,并且,所有的目標文件都寫在一個 Makefile 中,那么你可以這樣做:
all : prog1 prog2 prog3 .PHONY : allprog1 : prog1.o utils.occ -o prog1 prog1.o utils.oprog2 : prog2.occ -o prog2 prog2.oprog3 : prog3.o sort.o utils.occ -o prog3 prog3.o sort.o utils.oMakefile 中的第一個目標會被作為其默認目標。 我們聲明了一個 all 的偽目標,其依賴于其它三個目標。由于 默認目標總是被執(zhí)行的 ,而上面的 Makefile 文件中的第一個目標(默認目標) all 又是一個偽目標。因此 all 是一定會被執(zhí)行的,但又因為偽目標只是一個標簽不會生成文件,所以不會有 all 文件產(chǎn)生。于是,其它三個目標的規(guī)則總是會被執(zhí)行。也就達到了我們一口氣生成多個目標的目的。 .PHONY : all 聲明 all 這個目標為 偽目標 。(注:這里的顯式 .PHONY : all 不寫的話一般情況也可以正確的執(zhí)行,這樣 make 可通過隱式規(guī)則推導出, all 是一個偽目標,執(zhí)行 make 不會生成 all 文件,而是執(zhí)行后面的多個目標。建議:顯式寫出是一個好習慣。)
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的Linux | 编译原理、gcc的命令参数、自动化构建工具 make/Makefile的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《御姐玫瑰Z:神乐(Onechanbar
- 下一篇: 古天乐疑吐槽《复仇者联盟4》剧透行为:小