一:起因
(0)首先我們假設讀者已經熟悉了linux 下的vim的常用操作(如,u 撤消 或 :undo CTRL-R 重做 或 :redo);
(1)Linux下面的make命令,應用與makefile文件,當我們需要對一個C / C++ 或者java等一個工程里面的所有源文件進行編譯時,每一次編譯,都不想重復操作以前的命令,更不想把未更改過的源文件再次進行不必要的編譯操作 ——最佳選擇makefile(PS:雖然這時,你可以選擇寫一個shell,但是shell需要太多的代碼了,好多檢測和判斷需要自己來寫)
(2)make命令執行時,需要一個 Makefile 文件,以告訴make命令需要怎么樣的去編譯和鏈接程序。
Makefile的書寫規則是:1)如果這個工程沒有編譯過,那么我們的所有C文件都要編譯并被鏈接。2)如果這個工程的某幾個C文件被修改,那么我們只編譯被修改的C文件,并鏈接目標程序。3)如果這個工程的頭文件被改變了,那么我們需要編譯引用了這幾個頭文件的C文件,并鏈接目標程序。只要我們的Makefile寫得夠好,所有的這一切,我們只用一個make命令就可以完成,make命令會自動智能地根據當前的文件修改的情況來確定哪些文件需要重編譯,從而自己編譯所需要的文件和鏈接目標程序。
二:詳解
(1)首先簡介一下Linux GCC常用命令
1)簡單編譯(一步到位)
[cpp] view plain copy//hello.c #include <stdio.h> int main(void) { PRintf("Hello World!/n"); return 0; }這個程序,一步到位的編譯指令是:gcc hello.c -o hello
2)實質上,上述編譯過程是分為四個階段進行的,即預處理(也稱預編譯,Preprocessing)、編譯(Compilation)、匯編 (Assembly)和連接(Linking)(如下)
2.1預處理
gcc -E hello.c -o hello.i 或 gcc -E hello.c 可以輸出hello.i文件中存放著hello.c經預處理之后的代碼。打開hello.i文件,看一看,就明白了。后面那條指令,是直接在命令行窗口中輸出預處理后的代碼.gcc的-E選項,可以讓編譯器在預處理后停止,并輸出預處理結果。在本例中,預處理結果就是將stdio.h 文件中的內容插入到hello.c中了。
2.2編譯為匯編代碼(Compilation)
預處理之后,可直接對生成的hello.i文件編譯,生成匯編代碼:
gcc -S hello.i -o hello.s
gcc的-S選項,表示在程序編譯期間,在生成匯編代碼后,停止,-o輸出匯編代碼文件。
2.3匯編(Assembly)
對于上一小節中生成的匯編代碼文件hello.s,gas匯編器負責將其編譯為目標文件,如下:
gcc -c hello.s -o hello.o
2.4連接(Linking)
gcc連接器是gas提供的,負責將程序的目標文件與所需的所有附加的目標文件連接起來,最終生成可執行文件。附加的目標文件包括靜態連接庫和動態連接庫。對于上一小節中生成的hello.o,將其與C標準輸入輸出庫進行連接,最終生成程序hellogcc hello.o -o hello在命令行窗口中,執行./hello, 讓它說HelloWorld吧!
3)多個程序文件的編譯
通常整個程序是由多個源文件組成的,相應地也就形成了多個編譯單元,使用GCC能夠很好地管理這些編譯單元。假設有一個由hello1.c和 hello2.c兩個源文件組成的程序,為了對它們進行編譯,并最終生成可執行程序hello,可以使用下面這條命令:gcc hello1.c hello2.c -o hello如果同時處理的文件不止一個,GCC仍然會按照預處理、編譯和鏈接的過程依次進行。如果深究起來,上面這條命令大致相當于依次執行如下三條命令:
[cpp] view plain copygcc -c hello1.c -o hello1.o gcc -c hello2.c -o hello2.o gcc hello1.o hello2.o -o hello(2)makefile的書寫
0)Makefile的一般格式。
[cpp] view plain copytarget ... : prerequisites ... command ... ... 其中,target可以是一個目標文件、Object File或可執行文件;還可以是一個標簽(Label)如“偽目標” —— command產生的結果prerequisites 就是,要生成那個target所需要的文件或是目標 ——— 產生target所需要的依賴文件或目錄
command 是make需要執行的命令。(任意的Shell命令)可以看出,target 這一個或多個的目標文件依賴于prerequisites中的文件,其生成規則定義在command中;
prerequisites中如果,有一個以上的文件比target文件要新的話,command所定義的命令就會被執行---這就是Makefile的規則----也就是Makefile中最核心的內容。
1) 假設我有a.c,b.c, main.c三個源文件,最終要編譯成一個名為main的可執行文件
如果我想清除掉我編譯后的文件,可以rm命令,當然我們也可以清除的功能寫入到makefile里邊。makefile提供了一個偽目標的功能,可以為你的makefile提供額外的功能。下面是提供了clean功能的makefile文件中的代碼:
[plain] view plain copymain : a.o b.o main.o @gcc a.o b.o main.o -o main @echo ok @gcc -c main.c b.o : b.c @gcc -c b.c a.o : a.c @gcc -c a.c # 用".PHONY {目標名}"定義一個偽目標 # 用"make {目標名}"執行該偽目標 .PHONY : clean clean : @rm -f main *.o @echo clean2)解釋
makefile中,#代表注釋,每一段命令之后至少要有一個空行;具體的命令通過tab鍵來表示 .PHONY : clean 用來定義偽目標
執行make clean即可進入clean的功能
此外,makefile提供了系統默認的自動化變量$^:代表所有依賴文件$@:代表目標$<:代表第一個依賴文件所以我們可以用gcc $^ -o $@代替剛剛makefile中的gcc a.o b.o main.o -o main
3)makefile的高級應用:
在makefile中使用變量,就是一個字符串,可以理解成C語言中的宏;比如,我們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能
夠表示obj文件就行了。我們在makefile一開始就這樣定義:
objects = main.o kbd.o command.o display.o /insert.o search.o files.o utils.o
于是,我們就可以很方便地在makefile中以“$(objects)”的方式來使用這個變量了
三:實例舉證
(1)如果一個工程有3個頭文件,和8個C文件;為了完成(一)中的那三個規則,Makefile應該是:
[cpp] view plain copyedit : main.o kbd.o command.o display.o / insert.o search.o files.o utils.o gcc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o main.o : main.c defs.h gcc -c main.c kbd.o : kbd.c defs.h command.h gcc -c kbd.c command.o : command.c defs.h command.h gcc -c command.c display.o : display.c defs.h buffer.h gcc -c display.c insert.o : insert.c defs.h buffer.h gcc -c insert.c search.o : search.c defs.h buffer.h gcc -c search.c files.o : files.c defs.h buffer.h command.h gcc -c files.c utils.o : utils.c defs.h gcc -c utils.c clean : rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o 反斜杠(/)是換行符的意思;文件名為“Makefile”或“makefile”,然后在該目錄下直接輸入命令“make”,就可以生成執行文件edit;如果要刪除執行文件和所有的中間目標文件,那么,只要簡單地執行一下“make clean”就可以了。在這個makefile中,目標文件(target)包含:執行文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號后面的那些 .c 文件和 .h文件。每一個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關系的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。
在定義好依賴關系后,后續的那一行定義了如何生成目標文件的操作系統命令,一定要以一個Tab鍵作為開頭。記住,make并不管命令是怎么工作的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那么,make就會執行后續定義的命令。
這里要說明一點的是,clean不是一個文件,它只不過是一個動作名字,有點像C語言中的lable一樣,其冒號后什么也沒有,那么,make就不會自動去找文件的依賴性,也就不會自動執行其后所定義的命令。要執行其后的命令,就要在make命令后明顯得指出這個lable的名字。這樣的方法非常有用,我們可以在一個makefile中定義不用的編譯或是和編譯無關的命令,比如程序的打包,程序的備份,等等。
(2)在默認的方式下,也就是我們只輸入make命令:1、make會在當前目錄下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,并把這個文件作為最終的目標文件。
3、如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時間要比edit這個文件新,那么,他就會執行后面所定義的命令來生成edit這個文件。
4、如果edit所依賴的.o文件也不存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
5、當然,你的C文件和H文件是存在的啦,于是make會生成 .o 文件,然后再用 .o 文件生命make的終極任務,也就是執行文件edit了。
這就是整個make的依賴性,make會一層又一層地去找文件的依賴關系,直到最終編譯出第一個目標文件。在找尋的過程中,如果出現錯誤,比如最后被依賴的文件找不到,那么make就會直接退出,并報錯,而對于所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,如果在我找了依賴關系之后,冒號后面的文件還是不在,那么對不起,我就不工作啦。通過上述分析,我們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那么它后面所定義的命令將不會被自動執行,不過,我們可以顯示要make執行。即命令——“make clean”,以此來清除所有的目標文件,以便重編譯。
于是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如file.c,那么根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關系后面所定義的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改時間要比edit要新,所以edit也會被重新鏈接了(詳見edit目標文件后定義的命令)。而如果我們改變了“command.h”,那么,kdb.o、command.o和files.o都會被重編譯,并且,edit會被重鏈接。
(3)使用變量更改后:
[cpp] view plain copyobjects = main.o kbd.o command.o display.o / insert.o search.o files.o utils.o edit : $(objects) gcc -o edit $(objects) main.o : main.c defs.h gcc -c main.c kbd.o : kbd.c defs.h command.h gcc -c kbd.c command.o : command.c defs.h command.h gcc -c command.c display.o : display.c defs.h buffer.h gcc -c display.c insert.o : insert.c defs.h buffer.h gcc -c insert.c search.o : search.c defs.h buffer.h gcc -c search.c files.o : files.c defs.h buffer.h command.h gcc -c files.c utils.o : utils.c defs.h gcc -c utils.c .PHONY : clean clean : -rm edit $(objects)
新聞熱點
疑難解答