Makefile的初学教程

进入大二以后天天写代码,不如就重新使用自己的blog吧。感觉看自己之前写的东西重新学习还挺不错的。

Makefile可以帮助你编译。原来搞OI的时候都是一个cpp文件直接编译运行就行了。不过现在假设你要写一个很简单的小计算器,每一个cpp文件执行一种算术方法,你如果一个一个编译的话,大概是要写成这样的:

g++ -c -Wall main.cpp -o main.o
g++ -c -Wall add.cpp -o add.o
g++ -c -Wall sub.cpp -o sub.o
g++ -c -Wall mul.cpp -o mul.o
g++ -c -Wall div.cpp -o div.o

g++ -o main main.o add.o sub.o mul.o div.o

这样写起来很麻烦,所以Makefile应运而生。

1.基本规则

Makefile的核心规则(核心语法?)大概是这样的:
targets : prerequisites
commands

其中targets是目标文件(目前大多数你写的程序都只有一个执行文件,可以先理解为执行文件),prerequisites是用于生成target的文件或目标。
表示输入一个Tab缩进(这个非常重要!!),commands 是你要执行的命令(shell命令)。

写一个比较简单的,只有3个cpp的makefile示例:

main : main.cpp printhello.cpp factorial.cpp
  g++ -o main main.cpp printhello.cpp factorial.cpp

在终端输入make就可以运行makefile了。之后./main来执行程序。

2.使用变量

Makefile中使用变量其实很像C++中的宏(#define),大致写法是:

CC = g++
TARGET = main
OBJ = main.o printhello.o factorial.o

$(TARGET) : $(OBJ)
  $(CC) -o $(TARGET) $(OBJ)

其中,CC,TARGET,OBJ等赋值符号左边的叫做变量(variables),其实只看代码的话,和上一个是完全一样的,我们把宏替换一下就是一样的代码了。
这个主要是用于减少代码量,写起来更加简洁。

3.文件依赖(我也不知道是不是该叫这个名字)

有的时候只有一个或几个cpp被改动,我们是不需要重新编译所有文件的。设想如果有上百个cpp,只改动其中一个就要全部重新编译显然非常浪费时间,因此我们可以将我们的代码改写一下:

CC = g++
TARGET = main
OBJ = main.o printhello.o factorial.o

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


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


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

假如我们只改变了main.cpp,那么重新执行make的时候他就只会重新编译main.cpp,而不会把所有cpp都重新编译一次了。
不过现在的问题是代码量增加了很多,如果有上百个cpp我们显然是吃不消的,这时候就需要使用文件依赖。大致意思是,每一个.o文件都是.cpp文件编译而成的。所以我们将代码采取如下写法:

CC = g++
TARGET = main
OBJ = main.o printhello.o factorial.o
CFLAGS = -c -Wall

$(TARGET) : $(OBJ)
  $(CC) -o $@ $(OBJ)

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

#本段中$@表示所有目标文件,$^表示所有prerequisites文件,$<表示第一个prerequsites文件。
#因为每一个.o依赖于一个.cpp,所以这里写$<或者$^都是可以的。
#注意这里前后两个$@表示的文件是不同的,第一个表示的是TARGET(main),即可执行文件,而第二个表示的这个.o文件对应的.cpp文件。

注意其中一行%.o : %.cpp,这一段是一个规则,大致意思就是我们的文件依赖,所有的.o都是依赖于.cpp文件的。

4.使用函数

Makefile中的函数非常多,这里只简单介绍两种,wildcard和patsubst。
我们的代码已经很简洁了,但是看OBJ一行,如果我们有上百个.cpp,也就会有上百个.o,那手动输入进去还是很麻烦的。
解决方法就是使用wildcard(通配符)函数,这个函数可以自动搜索路径中的一类文件(我们这里先假定为cpp文件),用法如下:

SRC = $(wildcard ./*.cpp)

现在SRC就找到了路径中所有的.cpp文件,之后我们使用字符串函数patsubst,它可以批量处理字符串,我们的目标是把所有.cpp改成.o,用法如下:

SRC = $(wildcard ./*.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SRC))
target:
  @echo $(SRC)
  @echo $(OBJ)

target后面是输出语句,可以让你验证一下找的是不是正确的文件。

那么现在我们就可以非常简洁的写好makefile了。直接上最终版的代码吧。src是存储所有cpp文件的文件夹,inc是存储所有hpp(h)文件的文件夹,makefile文件应该和这两个文件夹处在一个子目录下。

#第一行这里写 = src也行
SRC_DIR = ./src
SOURCE = $(wildcard $(SRC_DIR)/*.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SOURCE))
TARGET = main
INCLUDE = -I./inc 
#这里是在inc里找所有引用的hpp文件

CC = g++
CFLAGS = -c -Wall

$(TARGET) : $(OBJ)
  $(CC) -o $@  $(OBJ)
%.o : %.cpp
  $(CC) $(CFLAGS) $< -o $@ $(INCLUDE)

.PHONY : clean
clean:
  rm -f $(SRC_DIR)/*.o $(TARGET)

最后.PHONY : clean这里是clean函数,用于清理多余的.o文件。使用方法是在终端输入make clean即可。

本文章只包含一些非常初级的makefile用法,如果想学更多,可以看这里或者这里

posted @ 2021-09-24 03:17  CaptainLi  阅读(826)  评论(0编辑  收藏  举报