make和makefile

1.前言

make和makefile的關係

make可以方便地構建和編譯一個大型的工程,整個工程的編譯只需要一個命令make就可以完成編譯、鏈接和運行。使用make命令之前,需要編寫makefile文件,這個文件詳細描述了我們整個工程該如何去編譯,連接以及最後生成一個可執行的文件,以及這個過程所需要依賴的文件和其他工具軟件。

編譯和鏈接

編譯: 編譯就是把文本形式的源代碼翻譯稱爲及其語言形式的目標文件的過程。
鏈接: 鏈接就是把目標文件和使用到的庫文件進行組織,最終形成可執行代碼的過程。

庫文件

在linux中庫文件可以分爲靜態庫和共享庫(有的地方也叫做動態庫)。

靜態庫: 它也稱爲歸檔文件(archive file),按照慣例它們的文件都是以”.a”結尾,比如標準C語言函數庫”/usr/lib/libc.a”。靜態庫在程序鏈接時會使用,鏈接器會將程序中使用到函數的代碼從靜態庫文件中拷貝到應用程序中。一旦鏈接完成,在執行程序的時候就不需要靜態庫了。由於每個使用靜態庫的應用程序都需要拷貝所用到函數的代碼,所以靜態鏈接生成的可執行文件都會比較大。

共享庫: 共享庫以”.so”結尾(so = shared objecct)。共享庫在程序鏈接的時候並不像靜態庫那樣在拷貝所使用到函數的代碼到應用程序中,而只是作些標記。然後在程序加載到內存中開始運行的時候,動態地加載所需的共享庫。所以,應用程序在運行的時候仍然需要共享庫的支持,共享庫鏈接出來的文件要比靜態庫要小的多。

2.make和makefile

2.1 make簡介
make是一個GNU/Linux中自帶一個使用軟件工具,它能夠自動地判斷出一個大的工程文件中那些源文件需要被重新編譯,並且可以直接使用特定的命令來對這些文件進行重新編譯。
2.2 makefile簡介
當你準備使用”make”命令來編譯你的工程文件前,你需要準備好一個叫做Makefile的文件。通常,這個文件會告訴make如何編譯和鏈接程序,在這個文件中主要描述了你程序中各個文件之間的關係並且提供了一些命令來編譯文件,最終生成可執行的目標文件。

當使用make工具進行編譯時,工程中以下幾種文件在執行make時將會被編譯(重新編譯):

1.所有的源文件都沒有被編譯過,則對各個源文件進行編譯和鏈接,生成最後可執行的程序。
2.每一個在上次執行make之後修改過的源文件在本次執行make時,都會被重新編譯。
3.頭文件在上次執行make之後被修改過,那麼所有包含該修改過頭文件的源程序文件都會在執行本次make命令時重新進行編譯。

2.3 make規則介紹
一個簡單的Makefile由以下這種類型的“規則”構成:

target…: prerequistes… [tab] command

**target: 規則目標。**target通常是最後需要生成的文件名或者爲了實現這個目的而必須的中間過程文件名,可以是目標文件名“*.o”或者最後可執行的程序的文件名。另外,目標也可以是一個make執行動作的的名稱,比如“clean”(目標”clean”不是一個文件,它僅僅代表執行一個動作的標識),我們將這樣的目標稱爲“僞目標”。

**prerequisites:規則依賴。**生成規則目標所需要的文件列表。通常一個目標依賴於一個或者多個文件。

command: 規則的命令行,是規則所要執行的動作(任意的shell命令或者是可在shell中執行的程序)。它限定了make執行這條規則時所需要的動作。一個規則可以有多個命令行,每一條命令佔一行。 注意: 每一個命令行必須以[Tab]字符開始,[Tab]字符告訴make此行是一個命令行。make按照命令完成相應的動作。這也是書寫Makefile中容易產生並且十分隱蔽的錯誤。

命令就是在任何一個目標的依賴文件發生變化後重建目標的動作描述。一個目標可以沒有依賴而只有動作(指定的命令)。比如Makefile中的目標“clean”,此目標沒有依賴,只有命令。它所定義的命令用來刪除make過程中生成的中間文件,進行清理工作。

在Makefile中“規則”就是描述在什麼情況下、如何重建規則的目標文件,通常規則中包括了目標的依賴關係(目標的依賴文件)和重建目標的命令。make執行重建目標的命令,來創建或者重建規則的目標(此目標文件也可以是觸發這個規則的上一個規則中依賴文件)。規則包含了文件之間的依賴關係和更新此規則目標所需要的命令。

一個Makefile文件中通常還包含了除規則以外的很多東西。一個最簡單的Makefile可能只包含規則。規則在有些Makefile中可能看起來非常複雜,但是無論規則的書寫是多麼複雜,它都符合規則的基本格式。

規則告訴make就是兩件事情,第一就是文件的依賴關係,第二就是如何生成目標文件

2.4 一個例子
爲了更好地理解Makefile,我們就通過一個簡單的例子來進行闡述說明。在這個例子中,我們有總共有三個文件file1.c,file2.c和file2.h,通過這三個文件我們要構造一個可執行的目標程序“helloworld”。

//file1.c
#include <stdio.h>
#include "file2.h"

int main()
{
	printf("print file1$$$$$$$$$$$$$$$$$$$$\n");
	File2Print();
	return 0;
}
//file2.h
#ifndef FILE2_H_
#define FILE2_H_
	#ifdef __cplusplus
		extern "C" {
	#endif
	void File2Print();
	#ifdef __cplusplus
		}
	#endif
#endif
//file2.c
#include <stdio.h>
#include "file2.h"

void File2Print()
{
	printf("Print file2********************\n");
}
helloworld:file1.o file2.o
	gcc file1.o file2.o -o helloworld
file1.o: file1.c file2.h
	gcc -c file1.c -o file1.o
file2.o: file2.c file2.h
	gcc -c file2.c -o file2.o
clean:
	rm -rf *.o helloworld
	```
	上面的makefile文件目的就是要編譯一個helloworld的可執行文件。讓我們一句句來解釋:
	```
	helloworld:file1.o file2.o  #目標helloworld依賴於file1.o和file2.o兩個目標文件。

	gcc file1.o file2.o -o helloworld  #使用gcc編譯出helloworld可執行文件。-o 表示執行輸出的目標文件名。
    
file1.o: file1.c file2.h   #file1.o依賴於file1.c和file2.h文件。

	gcc -c file1.c -o file1.o  #編譯出file1.o文件。-c表示gcc只把源文件編譯成目標文件。
    
file2.o: file2.c file2.h
	gcc -c file2.c -o file2.o
#這兩句的含義上面兩句的含義相同

clean:
	rm -rf *.o helloworld
#當用戶在shenll終端中輸入make clean的命令時,就會刪除*.o和helloworld文件。

寫好Makefile文件之後,只需要簡單地在終端命令行中輸入make命令,就會開始執行Makefile中的內容了。
2.4.1 使用變量
如果我們需要編譯cpp文件時,只需要把gcc改成g++就可以了。但是如果Makefile中有很多gcc,那不就很麻煩嘛。 下面我們就使用變量來改寫上面的Makefile文件。

OBJS = file1.o file2.o
CC = gcc
CFLAGS = -Wall -O -g
helloworld:$(OBJS)
	$(CC) $(OBJS) -o helloworld
file1.o:file1.c file2.h
	$(CC) $(CFLAGS) -c file1.c -o file1.o
file2.o:file2.c file2.h
	$(CC) $(CFLAGS) -c file2.c -o file2.o
clean:
	rm -rf *.o helloworld

在上面我們使用到了變量。如果要設定一個變量,你只要在一行的開始寫下這個變量的名字,後面跟一個“=”號,後面跟你要設定的這個變量的值。已有你要引用這個變量,寫一個“$“符號在變量名前,就表示引用變量的值。

CFLAGS = -Wall -O -g,解釋一下,這個是gcc編譯器的參數設置,並把它賦值給CFLAGS變量。

參數 意義
-Wall 輸出所有的警告信息
-O 在編譯時進行優化
-g 表示編譯debug版本

這樣寫的Makefile文件比較簡單,但很容易就會發現缺點,那就是要列出所有的c文件。如果你添加一個c文件,那就需要對Makefile文件進行修改,這在項目開發中是比較麻煩的。
2.4.2 使用函數
學到這裏,你可能發現在Makefile中既有變量,又有函數,這不就是在編程嗎?其實寫Makefile就是在編程,只不過使用不同的語言而已。

下面我們就使用函數來繼續改寫上面的Makefile。

CC = gcc
XX = g++
CFLAGS = -Wall -O -g
TARGET =  ./helloworld
%.o:%.c
	$(CC) $(CFLAGS) -c $< -o $@
%.o:%.cpp
	$(XX) $(CFLAGS) -c $< -o $@
SOURCES = $(wildcard *.c *.cpp)
OBJS = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES)))

$(TARGET):$(OBJS)
	$(XX) $(OBJS) -o $(TARGET)
    chmod a+x $(TARGET)
clean:
	rm -rf *.o helloworld

看到上面這個Makefile文件我們的感覺就是要炸了,什麼語法啊,看不懂啊,所以我們要一步一步來分析。

函數1: wildcard 功能: 產生一個所有以“.c”結尾的文件的列表 SOURCES = $(wildcard *.c *.cpp) #這句代碼表示產生一個所有以.c,.cpp結尾的文件的列表,然後存入變量SOURCES中。

函數: patsubst 功能: 匹配替換,有三個參數。第一個參數是一個需要匹配的式樣,第二個表示用什麼來替換它,第三個是一個需要被處理的由空格分隔的文件列表 OBJS = (patsubst(patsubst %.c,%.o,(patsubst %.cpp,%.o,$(SOURCES))) #這句代碼表示把文件列表中的所有的.c,.cpp字符變成.o,形成一個新的文件列表,然後存入OBJS變量中。

%.o:%.c
	$(CC) $(CFLAGS) -c $< -o $@
%.o:%.cpp
	$(XX) $(CFLAGS) -c $< -o $@

這幾句命令表示把所有的 .c , .cpp 文件編譯成.o文件。 這裏有三個比較有用的內部變量:

內部變量 含義
$@ 表示目標文件
$< 表示依賴文件列表中第一個依賴文件
$^ 表示整個依賴文件列表
$? 表示比目標文件還要新的依賴文件列表

chmod a+x $(TARGET)表示把helloworld強制變成可執行的文件。

到此,我們就可以編寫一個比較簡單通用的Makefile文件了。

上面的所有例子,都假定所有的文件都在同一個目錄下,不包括字目錄。
2.5 Make是如何處理一個Makefile文件
在默認情況下,也就是我們只在shell環境中輸入一個make命令,那麼make接下來會執行以下的操作:

1.make會在當前目錄下尋找一個名字叫做”Makefile”的文件。
2.然後make會在Makefile文件中找到第一個目標(target),並將該目標當做最終要生成的目標,例如上面例子中的“helloworld”。
3.在make執行第一條規則之前(例如我們上面例子中生成helloworld可執行程序的規則),首先需要把目標可執行程序(target)所依賴的其他目標文件(object)的規則先執行處理完(在上面的例子中就是要先生成目標文件file1.o和file2.o)。
4.當規則中依賴文件中源程序文件或者頭文件經過修改導致更新的時間比目標文件更新時或者目標文件根本不存在時,則這個規則就會進行編譯(complication)或者重建(recomplication)。
5.在編譯生成或者重新編譯生成第一個目標所需的依賴文件後,make最後就會執行第一條規則中的命令,將一個或者多個依賴文件鏈接生成最終的可執行程序。如果所依賴的目標文件更新的時間比原來已經存在的目標文件生成的時間還新的話,那麼也會進行目標重建。
2.6 清空目標文件的規則
每個Makefile都應該編寫一個清空目標文件(.o和執行文件)的規則,這不僅便於重編譯,也很利於保持文件的整潔。 一般的風格是:

clean:
	rm *.o helloworld

但是更爲穩健的方法是

.PHONY: clean
clean:
	-rm *.o helloworld
	```
	在這裏”.PHONY”的意思表示clean是一個“僞目標”,而在rm命令前機上一個小減號”-“表示的意思是:也許文件出現了問題,但是不要管,繼續做後面的事。當然,clean的規則不要放在文件的開頭,不然會被make以爲是默認的目標。所有有個不成文的規定是——”clean從來都是放在最後面的”。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章