程序的 編譯 和 鏈接
要先總結 make
和 makefile
,就需要先了解下面這個過程:
- 預編譯:也叫預處理,進行一些文本替換工作,比如將
#define
定義的內容,在代碼中進行替換; - 編譯:將預處理得到的代碼,進行詞法分析、語法分析、中間代碼……;如果是在Windows下,中間代碼就是
.obj
文件;在Linux系統下,中間代碼就是.o
文件; - 匯編:將編譯得到的匯編代碼,通過匯編程序得到 0 和 1 機器語言;
- 鏈接:鏈接各種靜態鏈接庫和動態鏈接庫得到可執行文件。
make 和 makefile 能干啥?
一個工程,那么多源文件,一堆的 cpp
和 h
文件,怎么編譯啊?編譯一個大型工程,如果Rebuild可能就需要好幾個小時,甚至十幾個小時,那我們就可能要問了。
- 如何像VS那樣,一鍵就能編譯整個項目?
- 如何修改了哪個文件,就編譯修改的那個文件,而不是重新編譯整個工程?
好吧,make
和 makefile
就能搞定這些。makefile
定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因為 makefile
就像一個Shell腳本一樣,其中也可以執行操作系統的命令。makefile
帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個 make
命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。
make
是一個命令工具,是一個解釋 makefile
中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make
,Visual C++的nmake
,Linux 下 GNU 的 make
。可見,makefile
都成為了一種在工程方面的編譯方法。make 命令執行時,需要一個 makefile
文件,以告訴 make
命令需要怎么樣的去編譯和鏈接程序。
現在,應該明白了吧。make
是一個命令,用來解析 makefile
文件;makefile
是一個文件,用來告訴 make
命令,如何編譯整個工程,生成可執行文件。再打個比方:
導演 == make
劇本 == makefile
演員 == MAKE調用的外部命令,如編譯器、鏈接器等
電影 == 生成的程序
解決問題舉例
怎么就出現了make這個東西了呢?還記得你入門C語言時,寫下的Hello World程序么?
#include <stdio.h> int main() { printf("Hello World\n"); return 0; }
當你在終端中輸入 gcc HelloWorld.c
命令時,就會生成一個 a.out
文件(如果不用 -o 參數指定輸出文件名的話,默認為 a.out),然后就可以神奇的使用 ./a.out
執行該文件,打印出了 Hello World
。這是一件讓初學者興奮的事情。問題來了,現在就僅僅是一個 HelloWorld.c
文件,如果有多個代碼文件,而多個代碼文件之間又存在引用關系,這個時候,該如何去編譯生成一個可執行文件呢?比如現在有一下源文件:
add.h
add.c
sub.h
sub.c
mul.h
mul.c
divi.h
divi.c
main.c
這些代碼文件的定義分別如下:
add.h 文件
#ifndef _ADD_H_ #define _ADD_H_ int add(int a, int b); #endif
add.c 文件
#include "add.h" int add(int a, int b) { return a + b; }
sub.h 文件
#ifndef _SUB_H_ #define _SUB_H_ int sub(int a, int b); #endif
sub.c 文件
#include "sub.h" int sub(int a, int b { return a - b; }
mul.h 文件
#ifndef _MUL_H_ #define _MUL_H_ int mul(int a, int b); #endif
mul.c 文件
#include "mul.h" int mul(int a, int b) { return a * b; }
divi.h 文件
#ifndef _DIVI_H_ #define _DIVI_H_ int divi(int a, int b); #endif
divi.c 文件
#include "divi.h" int divi(int a, int b) { if (b == 0) { return 0; } return a / b; }
main.c 文件
#include <stdio.h> #include "add.h" #include "sub.h" #include "mul.h" #include "divi.h" int main() { int a = 10; int b = 2; printf("%d + %d = %d\n", a, b, add(a, b)); printf("%d - %d = %d\n", a, b, sub(a, b)); printf("%d * %d = %d\n", a, b, mul(a, b)); printf("%d / %d = %d\n", a, b, divi(a, b)); return 0; }
你也看到了,在 main.c
中要引用這些文件,那現在如何編譯,生成一個可執行文件呢?
最笨的解決方法
最笨的解決方法就是依次編譯所有文件,生成對應的 .o
目標文件。參考如下:
$ gcc -c sub.c -o sub.o $ gcc -c add.c -o add.o $ gcc -c sub.c -o sub.o $ gcc -c mul.c -o mul.o $ gcc -c divi.c -o divi.o $ gcc -c main.c -o main.o
然后再使用如下命令對所生成的單個目標文件進行鏈接,生成可執行文件。
$ gcc -o main add.o sub.o mul.o divi.o main.o
然后就可以得到一個可執行程序 main
,可以直接使用 ./main
進行運行。還不錯,雖然過程艱辛,至少也可以得到可執行程序。那么有沒有比這更簡單的方法呢?如果一個項目,幾千個文件,這么寫下去,還不得累死人啊。辦法是有的,我接着總結。
使用makefile文件
使用上面那種最笨的辦法,效率是非常低得,當添加新的文件,或者修改現有文件時,維護起來也是非常難得。基於此,現在就來說說使用 makefile
文件來搞定這一切。
關於什么是 makefile
,在文章的開頭我就已經總結了,至於它和 make
的關系,在文章的開頭也說的非常清楚了,現在就來看看如何使用 makefile
來完成上面同樣的任務,生成一個 main
的可執行文件。
#target:dependency-file
main:main.o add.o sub.o mul.o divi.o
gcc -o main main.o add.o sub.o mul.o divi.o
main.o:main.c add.h sub.h mul.h divi.h
gcc -c main.c -o main.o
add.o:add.c add.h
gcc -c add.c -o add.o
sub.o:sub.c sub.h
gcc -c sub.c -o sub.o
mul.o:mul.c mul.h
gcc -c mul.c -o mul.o
divi.o:divi.c divi.h
gcc -c divi.c -o divi.o
clean:
rm -f *.o
上面就是 makefile
文件的內容,對於 makefile
的內容的編寫規則,這里先不說。
現在你可以在 makefile
的同目錄下執行 make
命令,然后就可以看到生成了一堆 .o
目標文件,還有那個可執行的 main
文件;接着運行 make clean,那些 .o
文件就全部被刪除了。為什么是這樣?好了,你先照着做一遍吧。
makefile文件編寫規則
上面只是給出了一個簡單的 makefile
文件,你肯定好奇這個 makefile
的書寫規則是什么樣子的?
makefile 的規則大體上就是以下格式:
target
是一個目標文件,可以是 Object File
(.o文件),也可以使最終的執行文件,而 dependency-file
是生成對應 target
所需要依賴的文件或者其它的 target,command 就是最終由 make 執行的命令。
上面說了一段話,簡短而言就是:生成一個 target,需要依賴的文件,而使用命令來將依賴文件生成對應的 target 的規則,是在 command 中定義的。如果 dependency-file
中有一個或者多個文件比 target
文件要新的話,command
所定義的命令就會被執行,這就是 makefile
的規則,也就是 makefile
最核心的內容。
makefile
文件中可以定義變量,可以使用函數,還有各種判斷,內容繁多,這里就不一一總結了,更詳細的介紹,可以看看大牛陳皓的系列博客《跟我一起寫makefile》。
參考:
http://www.jellythink.com/archives/810?utm_source=tuicool&utm_medium=referral
http://bbs.csdn.net/topics/390143962