C 語言程式設計教學:使用 Make

PUBLISHED ON JUN 6, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Email Email

    註:本篇偏向概論,筆者在這裡撰寫一些較長的 GNU Make 教學,歡迎參考。

    首先,我們澄清一些觀念:對於 C (或 C++) 來說,make 不是必備的;make 的用途也不限於編譯 C (或 C++) 程式。make (1) 是類 Unix 系統上一個通用的編譯自動化 (build automation) 軟體,只要能夠用命令列工具操作的軟體,基本上都可以用 make 來自動化。比起直接撰寫 shell script,make 命令稿 (預設為 Makefile) 可以自動處理任務相依性;因此,比起等效的 shell script,Makefile 的語法更加簡潔。

    由於 Makefile 的語法不太好寫,如果不喜歡其語法,也有數個替代方案,像是:

    每個流程自動化軟體的語法各自不同,但本質上其實相差不遠。這類工具的思維是在開發時可以用相同的程式語言來撰寫設定檔以及用相同執行環境的軟體來管理任務,而不用額外安裝 make (或其他外部工具)。

    我們這裡仍然使用 make(1) 是因為 C (或 C++) 本身沒有內建的軟體編譯系統,不論用什麼工具都要額外安裝軟體,而 make 算是類 Unix 系統上老牌的工具,要找資料會比較好找。即使讀者使用 Windows 家族系統,也可裝 GNU Make 在 Windows 上的移植品 (從 MSYS2GnuWin32)。

    註:在 Windows 家族系統上,make 的移植品可能命名為 mingw32-make 或其他名字,需根據實際狀況修改指令名稱。

    Makefile 的思維其實相當簡單,用以下的「虛擬碼」即可表示:

    task: dependencies
    	command
    

    Makefile 由任務組成,而每個任務包括以下三個部分:

    • task:任務名稱
    • dependencies:相依的任務
    • command:實際的終端機指令

    例如,我們寫一個簡單的任務:

    compile:
    	gcc -Wall -g -o file file.c
    

    在命令列輸入 make compile 即可執行 compile 任務,在本例中,compile 任務會呼叫 GCC 來編譯一個 C 程式碼。

    我們想要在執行某任務前自動執行相關的任務:

    run: compile
    	./file
    
    compile:
    	gcc -Wall -g -o file file.c
    

    在命令列輸入 make run 時,會引發 run 任務,run 任務會先完成 compile 任務後,再完成本身的任務。如果 compile 任務失敗時,run 任務不會執行。例如,在 file.c 不存在的情形下執行 make run 會得到以下結果:

    $ make run
    gcc -Wall -g -o file file.c
    gcc: error: file.c: No such file or directory
    gcc: fatal error: no input files
    compilation terminated.
    make: *** [compile] Error 4
    

    我們可以將 Makefile 進一步簡化:

    all: run
    
    run: compile
    	./file
    
    compile:
    	gcc -Wall -g -o file file.c
    

    在這個例子中,我們只要輸入 makemake 會自動搜尋 all 任務,而 all 任務又指向 run 任務,run 任務又指向 compile 任務,依序完成相關任務。

    一般看到的 Makefile 會比較複雜,這是因為我們想要讓 Makefile 具有通用性。以下是筆者練習串列 (linked list) 時所用的 Makefile:

    CFLAGS=-Wall -Wextra -g -std=c99
    MEM_CHECK=valgrind
    TARGET=test_list.out
    
    .PHONY: all check run compile trim clean
    
    all: run
    
    check: compile
    	$(MEM_CHECK) ./$(TARGET)
    	echo $$?
    
    run: compile
    	./$(TARGET)
    	echo $$?
    
    compile: trim
    	$(CC) $(CFLAGS) -o $(TARGET) test_list.c \
    		test_manipulation.c test_traversal.c \
    		list.c
    
    trim:
    	perl -lpi -e "s{\s+$$}{}g;" *
    
    clean:
    	$(RM) $(TARGET) *.o
    

    註:在 Makefile 中,CC 內定指向 gcc,RM 內定指向 rm -f,故不需另外設置。

    在這個 Makefile 中,有三個指令,make 可執行測試程式,make check 可進行記憶體檢查,而 make clean 會清掉產生的執行檔。如果某人使用此 Makefile,但想使用 clang 編譯 C 程式碼,只要將 CCCFLAGS 的部分代換掉即可。

    Makefile 有許多複雜的語法,我們這裡大概只用了不到 3% 的功能,這是為了適應不同的情境而實作的特性,一開始時其實不用學那麼深,前述簡單的語法即可滿足初期的需求,有需要時再去查詢即可。

    Autotools 是一套可以根據不同系統環境自動產生相對應的 Makefile 的工具,要不要學呢?Autotools 的出發點是用來發布原始碼形式的應用程式或函式庫,可簡化在異質系統間編譯原始碼的流程,如果只是初期練習 C (或 C++),其實也用不到這類進階的工具。另外,Windows 系統不支援 Autotools,如果要在 Windows 上寫 C (或 C++),不用刻意學 Autotools。