iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
自我挑戰組

Lex & Yacc 學習筆記系列 第 13

[Day13] Makefile 介紹

  • 分享至 

  • xImage
  •  

本篇內容

  • 介紹Makefile

Makefile 介紹

今天的內容跟Lex & Yacc比較沒關係。我們要來介紹Makefile(簡稱Make檔或製作檔案)。

還記得昨天我們有提過Lex & Yacc的編譯嗎?從Yacc、Lex到主程式,編譯的流程逐漸變得複雜且繁瑣。有沒有辦法可以直接一次執行所有的編譯指令呢?有了Makefile,便可以自己定義編譯規則與順序,自動化建構專案整體的編譯。

接下來我們就來簡單介紹Makefile的語法,以及其基本應用。

Makefile 語法元素

  1. 目標(Targets)Target是要構建的文件或操作的名稱。通常,Target代表執行檔或其他文件。它們出現在 Makefile 的開頭位置,由冒號(:)分隔。例如:

    target: dependencies
        command
    
  2. 依賴(Dependencies)DependencyTarget所依賴的文件或其他Target。當Dependency中的任何一個文件發生變化時,Target需要重新構建。Dependency出現在冒號(:)之後,用空格分隔。例如:

    target: dependency1 dependency2
        command
    
    
  3. 規則(Rules):規則是指定如何生成Target的說明。它們出現在TargetDependency之間,以 Tab 開頭。一個Target可以有多個規則。例如:

    target: dependency
        command1
        command2
    
  4. 命令(Commands):命令是實際執行的指令,用於生成Target。命令必須以 Tab 開頭。例如:

    target: dependency
        gcc -o target dependency
    
  5. 註釋(Comments):註釋以 "#" 字符開頭,用於添加說明和說明 Makefile 中的部分。例如:

    # 這是一個簡單的Makefile註釋
    

Makefile 實例

以下是一個簡單的 Makefile 範例,該範例用於編譯一個名為 "hello" 的 C 程式:

# Makefile範例

# Target:編譯"hello"程式
hello: hello.c
    gcc -o hello hello.c

# 清理Target:刪除生成的執行檔
clean:
    rm -f hello

這個 Makefile 包含兩個Target。第一個Target是 "hello",它依賴於 "hello.c" 程式檔。當運行 make hello 時,它將使用 gcc 編譯 "hello.c" 文件並生成 "hello" 執行檔。第二個Target是 "clean",它用於刪除生成的執行檔。運行 make clean 時,它將刪除 "hello" 執行檔。

由於”hello”是第一個Target,若是執行命令只下”make”的時候,Makefile會自動抓取第一個Target,故產生的結果與”make hello” 指令相同。

Makefile 多檔案編譯

當我們遇到多檔案的編譯時,Makefile執行時會逐條比對規則。若某規則的所有input均滿足,才會執行該規則。否則,Makefile會先執行其他可以先執行的規則,最後再回去執行該規則。

這聽起來有點像繞口令,我們來看看以下的例子。

gcc main.cpp sub.cpp -o main

這個指令寫成Makefile的結果如下:

main: main.o sub.o
    gcc main.o sub.o -o main
main.o: main.cpp
    gcc main.cpp -c
sub.o: sub.cpp
    gcc sub.cpp -c
clean:
    rm -rf main.o sub.o

當執行make指令後,讀取此Makefile的流程如下:

  • Makefile執行後第一個抓到的target為main, main需要main.o跟sub.o這兩個目的檔。如果gcc找得到這兩個目的檔,才會開始執行main規則。
    • 很不巧,gcc無法找到這兩個檔案(因為還沒有編譯),因此gcc會尋找第一個dependency,也就是main.o,接續main.o的規則。
  • 到了main.o,其dependency是main.cpp。 main.cpp就在這個目錄下,因此gcc終於可以執行第一個command(gcc main.cpp -c),產生main.o,並回到main規則
  • 有了main.o,gcc繼續尋找第二個dependency (sub.o)
  • 於是進入sub.o 規則,找到了sub.cpp,執行此規則的command (gcc sub.cpp -c),產生了sub.o。
  • 再次回到main規則,發現此時所有dependencies都滿足了,終於可以開始進行真正的command,把所有的obj編譯成main這隻程式。

要注意的是,如果我們修改程式碼,再一次執行make,會發生甚麼事呢?

  • 第一步與剛剛相同,Makefile執行後第一個抓到的target為main, main需要main.o跟sub.o這兩個目的檔。如果gcc找得到這兩個目的檔,才會開始執行main規則。
    • 然而,此時兩個目的檔已經存在,因此Makefile直接執行main規則,產生出的main檔案與先前的檔案相同,沒有編譯到修改的程式部分。

所以,剛剛介紹的clean規則就很重要了。在編譯之前,最好先下make clean指令清除先前產生的執行檔,重新編譯後才會得到最新的編譯成果。

Makefile 變數宣告

Makefile的變數宣告格式如下:

${VAR}

剛剛的例子中,如果我們把C++ compiler設成變數,Makefile可以改寫成這樣:

CC = gcc

main: main.o sub.o
    ${CC} main.o sub.o -o main
main.o: main.cpp
    ${CC} main.cpp -c
sub.o: sub.cpp
    ${CC} sub.cpp -c
clean:
    rm -rf main.o sub.o

若是我們想要改成用g++來編譯,就只要更改最上面的compiler變數數值即可。

經過了上述的介紹,我們來試試看把昨天的編譯流程寫成一個Makefile吧!

範例 - Makefile撰寫

說明

請將下面的Lex & Yacc編譯流程寫成一個Makefile。

bison -d yacc.y
flex lex.l
gcc -c yacc.tab.c
gcc -c lex.yy.c
gcc -c main.cpp
gcc main.o lex.yy.o yacc.tab.o -o main

程式實作

首先,我們先來看看每一步的編譯過程的輸入與輸出分別為何。

如果忘記的話,再用之前的流程圖複習一次:

https://ithelp.ithome.com.tw/upload/images/20230913/20157613cRukqWEoJ1.png

bison -d yacc.y
	Input: yacc.y 
	Output: yacc.tab.h, yacc.tab.c
flex lex.l
	Input: lex.l, y.tab.h
	Output: lex.yy.c
gcc -c yacc.tab.c
	Input: yacc.tab.c
	Output: yacc.tab.o
gcc -c lex.yy.c
	Input: lex.yy.c
	Output: lex.yy.o
gcc -c main.cpp
	Input: main.cpp
	Output: main.o
gcc main.o lex.yy.o yacc.tab.o -o main
	Input: main.o, lex.yy.o, yacc.tab.o
	Output: main (main.exe)

接著,根據每一步,我們分別建立起編譯規則:

LEX=flex
YACC=bison
CC=g++
OBJECT=main

$(OBJECT): lex.yy.o yacc.tab.o main.o
		$(CC) main.o lex.yy.o yacc.tab.o -o $(OBJECT)

lex.yy.o: lex.yy.c yacc.tab.h main.h
		$(CC) -c lex.yy.c

yacc.tab.o: yacc.tab.c main.h
		$(CC) -c yacc.tab.c

yacc.tab.c yacc.tab.h: yacc.y
		$(YACC) -d yacc.y

lex.yy.c: lex.l
		$(LEX) lex.l

main.o: main.cpp
		$(CC) -c main.cpp

clean:
		@del -f $(OBJECT) *.o lex.yy.c yacc.tab.h yacc.tab.c main.exe

由於我所使用的環境為Win10,故clean的寫法修正如上。

這裡我們在command前加上一個@符號,意思是不把執行命令輸出到螢幕,僅輸出結果。這樣一來,terminal的訊息將更加簡潔。

結語

今天我們透過Makefile將複雜的Parser編譯流程自動化,以後就不再需要在terminal輸入一大堆編譯指令了~~

明天起,我們會介紹更多Yacc強大的語法功能,可以讀入更複雜的文本字串喔!

參考資料


上一篇
[Day12] Parser解析(3) 主程式與執行
下一篇
[Day14] Yacc - OR語法
系列文
Lex & Yacc 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言