當前位置: 華文問答 > 數位

如何系統地學習 Makefile 相關的知識(讀/寫)?

2015-08-10數位

Makefile其實不難學,對於一些基本概念百度上應該很多,這裏分享一個循序漸進的學習方式,保證讓你快速掌握Makefile的編寫。

本文先發於公眾號: 良許Linux ,裏面有一個 Makefile 系列,歡迎關註交流!

另外,想進大廠的同學,僅僅學會Makefile是不夠的,一定要好好學演算法,這是面試必備的。這裏準備了一份 BAT 大佬總結的 LeetCode 刷題寶典,很多人靠它們進了大廠。

1. 前言

透過之前章節的學習,我們對Makefile有個基礎的認識,現在開始自己動手寫Makefile。

目前網路上有不少可以自動生成Makefile的工具,但很多計畫其實沒必要那麽復雜,完全可以自己動手寫出來。

而且對於初學者來說,自己動手寫一遍Makefile可以頂看十遍高手寫的Makefile,也可以加深對Makefile的理解,將來公司的Makefile有需要修改的時候自己就可以動手搞定,不需要依靠他人,何樂而不為?

2. 原始碼介紹

在本教程中用於範例的程式碼很簡單,僅僅是在main函式中呼叫了fun1及fun2函式,而fun1及fun2獨立寫在fun1.c及fun2.c裏。程式碼如下:

//main.c int main() { printf("hello world\n"); fun1(); fun2(); } //fun1.c void fun1() { printf("this is fun1\n"); } //fun2.c void fun2() { printf("this is fun2\n"); }

3. 第一版Makefile

對於我們的範例程式碼,不透過Makefile編譯其實也很簡單:

gcc main.c fun1.c fun2.c -o app

我們知道,Makefile其實就是按規則一條條的執行。所以,我們完全可以把上面那條命令寫成Makefile的一個規則。我們的目標是app,按此寫法依賴是main.c fun1.c fun2.c,則最終的Makefile如下:

app: main.c fun1.c fun2.c gcc main.c fun1.c fun2.c -o app

但這個版本的Makefile有兩個很重要的不足:

  1. 對於簡單程式碼還好,而對於大型計畫,具有成千上萬程式碼來說,僅用一行規則是完全不夠的,即使夠的話也需要寫很長的一條規則;
  2. 任何檔只要稍微做了修改就需要整個計畫完整的重要編譯。

基於此,我們在第一版的基礎上最佳化出第二版。

4. 第二版Makefile

在第二版Makefile中,為了避免改動任何程式碼就需要重新編譯整個計畫的問題,我們將主規則的各個依賴替換成各自的中間檔,即main.c --> main.o,fun1.c --> fun1.o,fun2.c --> fun2.o,再對每個中間檔的生成各自寫條規則比如對於main.o,規則為:

main.o: main.c • gcc -c main.c -o main.o

這樣做的好處是,當有一個檔發生改動時,只需重新編譯此檔即可,而無需重新編譯整個計畫。完整Makefile如下:

app: main.o fun1.o fun2.o gcc main.o fun1.o fun2.o -o app main.o: main.c gcc -c main.c -o main.o fun1.o: fun1.c gcc -c fun1.c -o fun1.o fun2.o: fun2.c gcc -c fun2.c -o fun2.o

第二版Makefile同樣具有一些缺陷:

  1. 裏面存在一些重復的內容,可以考慮用變量代替;
  2. 後面三條規則非常類似,可以考慮用一條模式規則代替。

基於此,我們在第二版的基礎上最佳化出第三版。

5. 第三版Makefile

在第三版Makefile中,我們使用變量及模式規則使Makefile更加簡潔。使用的三個變量如下:

obj = main.o fun1.o fun2.o target = app CC = gcc

使用的模式規則為:

%.o: %.c • $(CC) -c $< -o $@

這條模式規則表示:所有的.o檔都由對應的.c檔生成。在規則裏,我們又看到了兩個自動變量:$<和$@。其實自動變量有很多,常用的有三個:

$<:第一個依賴檔;

$@:目標;

$^:所有不重復的依賴檔,以空格分開

obj = main.o fun1.o fun2.o target = app CC = gcc $(target): $(obj) $(CC) $(obj) -o $(target) %.o: %.c $(CC) -c $< -o $@

第三版Makefile依然存在一些缺陷:

  1. obj對應的檔需要一個個輸入,工作量大;

2. 檔數目比較少時還好,檔數目一旦很多的話,obj將很長;

3. 而且每增加/刪除一個檔,都需要修改Makefile。

基於此,我們在第二版的基礎上最佳化出第四版。

6. 第四版Makefile

在第四版Makefile中,我們隆重推出了兩個函式: wildcard patsubst

wildcard:

擴充套件通配符,搜尋指定檔。在此我們使用src = $(wildcard ./*.c),代表在當前目錄下搜尋所有的.c檔,並賦值給src。函式執行結束後,src的值為:main.c fun1.c fun2.c。

patsubst:

替換通配符,按指定規則做替換。在此我們使用

obj = $(patsubst %.c, %.o, $(src))

代表將src裏的每個檔都由.c替換成.o。函式執行結束後,obj的值為main.o fun1.o fun2.o,其實跟第三版Makefile的obj值一模一樣,只不過在這裏它更智慧一些,也更靈活。

除了使用patsubst函式外,我們也可以使用模式規則達到同樣的效果,比如:

obj = $(src:%.c=%.o)

也是代表將src裏的每個檔都由.c替換成.o。

幾乎每個Makefile裏都會有一個偽目標clean,這樣我們透過執行make clean命令就是將中間檔如.o檔及目的檔全部刪除,留下幹凈的空間。一般是如下寫法:

.PHONY: clean clean: • rm -rf $(obj) $(target)

.PHONY代表聲明clean是一個偽目標,這樣每次執行make clean時,下面的規則都會被執行。

src = $(wildcard ./*.c) obj = $(patsubst %.c, %.o, $(src)) #obj = $(src:%.c=%.o) target = app CC = gcc $(target): $(obj) $(CC) $(obj) -o $(target) %.o: %.c $(CC) -c $< -o $@ .PHONY: clean clean: rm -rf $(obj) $(target)

7. 總結

Makefile其實也並不難,但關鍵的是一定要自己動手寫,這樣才會更加加深理解,否則也容易造成眼高手低。如果實在不知道從何下手,可以嘗試按上面的教程,一步步寫下來,也只需要寫四個版本而已,寫完了相信就有了初步的理解。

學習編程,千萬不要急於求成,一定要多讀一些經典書籍,多看源碼,多下苦功夫去死磕程式碼,這樣技術才能長進。給大家分享一些程式設計師必讀經典書籍,一定要多讀幾遍:

對應書單:

附:近期高贊回答

Linux的功能有多強大?
學習Linux有沒有比【鳥哥的Linux私房菜】更好的書?
有沒有學習Linux比較好的入門書籍?

碼字不易,硬核碼字更難,希望大家不要吝嗇自己的鼓勵。我是:

@程式設計師良許

歡迎關註我!

我的個人區域網絡站,滿滿的都是Linux幹貨:良許Linux教程網

如果本文對你有幫助,歡迎點贊、收藏、轉發給朋友,讓我有持續創作的動力!