Makefile文件简介
Makefile文件定义了一系列的规则,来指定工程中的哪些文件需要编译,哪些文件需要先编译后编译;其次Makefile文件可以像shell脚本一样,也可以执行一些操作系统的一些命令.
Makefile文件带来的好处是一旦写好了Makefile,只需要一个make命令便可以实现自动化编译.
关于程序的编译和链接
一般来说,C/C++程序生成可执行文件的过程一般是:先将源代码文件编译程中间文件,在linux也就是.o文件,这个过程叫编译;然后我们将大量的.o文件合成为可执行文件,这个过程叫程序的链接.
总的来说就是,首先源文件-> .o文件,再由.o文件->可执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的.o文件中找寻函数的实现,如果找不到,那到就会报链接错误码(Link Error).
Makefile编写规则:
target: prerequisite
command
target是我们想要生成的一个目标文件,可以是中间件文件即.o文件也可以是可执行文件,还可以是一个标签label(如clean);prerequisite是要生成这个target所需要的文件(可以是源文件.cpp也可以是.o文件);command是要执行的命令,shell命令.
一个示例:
hhy : main.o hhy.o
cc -o hhy main.o hhy.o
main.o : main.cpp def.h
cc -c main.cpp
hhy.o : hhy.cpp def2.h
cc -c hhy.cpp
clean:
rm -rf hhy main.o hhy.o
注意:
- 在command那一行,开头必须要以Tab键开头
- 最后的clean不是一个文件,而是一个动作的名字,可以看做一个label.其:后面什么都没有,make时也不会自动去找文件的依赖性.要执行这个命令就需要在make时加上这个label,即为我们常用的make clean命令.
- -c的作用是指定生成为可重连接的.o文件.
make命令是怎么执行呢
1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件.
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“main”这个文件,并把这个文件作为最终的目标文件.
3、如果main文件不存在,或是main所依赖的后面的 .o 文件的文件修改时间要比main这个文件新,那么,它就会执行后面所定义的命令来生成main这个文件.
4、如果main所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件.(这有点像一个堆栈的过程)
5、当然,你的Cpp文件和h文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件hhy了.
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
Makefile文件中使用变量
当我们的项目里的文件过多时,使用这种方法并不高效,而且修改或者添加都会比较麻烦,我们可以借助于Makefile文件中的变量的用法,可以将其理解为c语言中的宏代替.
如上面的例子中:
我们声明一个变量,叫obj,objfile,objs等等,我们的Makefile文件就可以这样定义:
objs = main.o hhy.o
hhy : $(objs)
cc -o hhy $(objs)
main.o : main.cpp def.h
cc -c main.cpp
hhy.o : hhy.cpp def2.h
cc -c hhy.cpp
clean:
rm -rf hhy $(objs)
所以如果我们之后想要添加.o文件便可以直接在objs后面添加即可.
Makefile文件编写的简化
Makefile的常用符号:
$^ 代表所有的依赖文件
$@ 代表所有的目标文件
$< 代表第一个依赖文件
除此之外,我们还可以借助于通配符来实现更为便利的Makefile文件.
比如上面的Makefile文件可以改写为
CC=g++
C11=-std=c++11
CFLAGS=-lpthread
OBJS = main.o hhy.o kk.o ll.o
TARGET=hhy
$(TARGET) : $(OBJS)
$(CC) $(C11) $(OBJS) -o $(TARGET) $(CFLAGS)
%.o : %.c
$(CC) $(C11) -c $^ -o $@
.PHONY : clean
clean:
rm -rf hhy $(objs)
- 这里的%.o : %.c 这代表的意思就是所有的.o文件依赖相应的.C文件,是一个模式规则。这样一来,make命令就会自动将所有的.c源文件编译成同名的.o文件.
- .PHONY代表clean是一个为目标.
Makefile文件中的几个关键字
在Makefile文件中,通配符会被自动展开,但在变量的定义和函数引用时,通配符讲实效,这种情况下就需要使用函数"wildcard",其用法为:
$(wildcard PATTERN...)
在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表,一般我们可以使用(wildcard *.c)来获取工作目录下的所有的.c文件列表.
还有其他几个关键字配合使用:
patsubst:替换通配符.
例如:(patsubst %.c,%.o,$(wildcard *.c)) 含义为:使用wildcard函数获取工作目录下的.c文件列表,之后将列表的所有文件名的后缀.c替换为.o文件.
具体综合的例子:
CC = g++ //指定编译命令为g++
CFLAGS = -lpthread -g -std=c++11 -w //g++参数
TARGET = Server //要生成的target
SrcFile = $(wildcard *.cpp) //当前目录下所有的.cpp文件
ObjFile = $(patsubst %.cpp,%.o,$(SrcFile)) //中间件文件,即.o文件
$(TARGET):$(ObjFile)
@$(CC) $(ObjFile) -o $(TARGET) $(CFLAGS) //前面加@在编译的过程中不会有提示输出
%.o:%.cpp
@$(CC) $(CFLAGS) -c $< -o $@ //生成objfile里面的.o文件
.PHONY:
clean
clean:
rm -rf *.o Server
更复杂的情况就是在定义变量的过程中添加路径:
CFLAGS = -lpthread -lm
CPPFLAGS = -I ./include //-I表示目录作为第一个寻找头文件的目录
SRCDIR = ./src //里面存放的.cpp文件
OBJDIR = ./obj //将生成的.o文件放在obj文件夹下
src = $(wildcard $(SRCDIR)/*.cpp) //获取展开src文件夹下所有的.cpp文件
obj = $(patsubst $(SRCDIR)/%.cpp, $(OBJDIR)/%.o, $(src)) //将src中的.cpp文件全部替换为obj文件下的.o文件
target = ./bin/HttpServer //目标文件
$(target):$(obj)
g++ $(obj) -o $@ $(CPPFLAGS) $(LDFLAGS)
$(OBJDIR)/%.o:$(SRCDIR)/%.cpp
g++ -c $< -o $@ $(CPPFLAGS) $(LDFLAGS)
.PHONY:clean
clean:
rm -f $(target) $(obj)
补充:
例如:gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld
其中-I表示将目录变为寻找头文件的第一个目录,其次还有-L表示目录作为第一个寻找库文件的目录,-l(小L)表示在上面的lib的路径中寻找libworld.so动态库文件.