这篇博客将从最简单的makefile讲起,通过不断升级,最后成为项目中常用的makefile文件。
入门
效果预览
1.一个最简单的输出"Hello world"的hello.c文件,内容如下:
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
2.一个makefile文件,内容如下:
start:
gcc -o hello hello.c
这是最简单的makefile文件,写好之后直接在目录下make,生成hello的可执行文件.
语法浅析
一、@
由此观之,最基本的格式就是
target:[dependency]
command
target 就是标号的意思,target可以随便叫,一般情况下要有实际意义,后面会解释他的其他用途。
[dependency] 是可选项,表示依赖关系,这里可以先不考虑。
command Linux下的命令,比如可以写
start:
echo "Welcome"
那么执行make之后就会在终端打印Welcome,效果如图:
你可能会疑惑,为什么会把命令echo "Welcome"显示出来,默认下我们的命令都会显示出来,要隐藏也很简单,只要在命令前加个@就可以了,试一试?
start:
@echo "Welcome"
二、标号
删掉刚刚生成的hello可执行文件,再将makefile修改成如下,make有什么效果?
start:
@echo "Welcome"
second:
@gcc -o hello hello.c
没错他只打印了一个"Welcome"并没有生成响应的可执行文件,也就是标号second里面的command并没有执行,原因是这样的在makefile中默认只执行第一个标号,想执行其中某个标号的话需要这样操作:
make 标号 .所以试一下make second,这样只执行seond,不打印Welcome但是生成了hello文件.
有什么意义?
start:hello.o
@gcc -o hello hello.c
hello.o:
@gcc -o hello.o -c hello.c
clean:
@rm -rf hello.o hello
直接make clean就可以删除刚刚生成的文件,注意不要把hello.c加入rm中。
三、make -f
make命令默认在当前目录下寻找文件名为makefile的文件并解析执行,如果想make一个文件名为aaa的文件怎么做?
make -f 文件名 就可以执行指定的文件。说实话不常用
进阶
一、依赖
还是先看效果,执行如下makefile
start:hello.o
@echo "That"
@gcc -o hello hello.c
hello.o:
@echo "This"
@gcc -o hello.o -c hello.c
结果如下:
This
That
可以发现先执行了hello.o中的代码,并且生成了hello.o文件,start后面的依赖表示告诉make需要有hello.o才可以执行以下代码,make会自动寻找hello.o执行完毕后,在回到start中执行。所以如果生成hello.o后再次执行make就不会执行,hello.o下面的命令,这在大型项目中的优势就很明显,这样可以节省很多编译的时间,直接链接生成可执行文件。
二、编译出错
gcc出错或者makefile语法错误都会报错,如果是gcc语法出错,makefile也会终止。
如果makefile执行成功,可以向c语言一样打印一个成功编译的信息,需要注意的是makefile编写不能向c语言一样随意,空格什么都要严格控制。
start:hello.o
@gcc -o hello hello.c
@echo "----------OK"
hello.o:
@gcc -o hello.o -c hello.c
clean:
@rm -rf hello.o hello
再进阶
一、变量
变量的定义:
变量一般大写,不需要定义类型,直接使用,可以是命令
CC=GCC #不要乱加空格
也可以是文件名字
SRCS=hello.c
OBJS=hello.o
EXEC=hello
变量的使用:
$(变量名)
所以代码如下:
CC=gcc
SRCS=hello.c
OBJS=hello.o
EXEC=hello
start:$(OBJS)
@gcc -o $(EXEC) $(SRCS)
@echo "----------OK"
hello.o:
@gcc -o $(OBJS) -c $(SRCS)
clean:
@rm -rf $(OBJS) $(EXEC)
有什么意义?
与宏定义类似,如果下面有修改,直接修改定义的一处就可以了,比如说换编译器了(g++),比如说文件重命名了。
二、升级
为了简化项目维护,还可以在升级
OBJS=$(SRCS:.c=.o) #注意空格不要乱加
代表SRCS变量的.c和.o文件有关系。
CC=gcc
SRCS=hello.c
OBJS=$(SRCS:.c=.o)
EXEC=hello
start:$(OBJS)
@gcc -o $(EXEC) $(SRCS)
@echo "----------OK"
hello.o:
@gcc -o $(OBJS) -c $(SRCS)
clean:
@rm -rf $(OBJS) $(EXEC)
三、模式规则
这看起来很鸡肋,但这只是一个过渡,在第一行加入.SUFFIXES:.c .o代表所有的.c和.o文件有关系(注意空格小细节),还不够强再加上$@和
$<。
$@ 规则目标文件名 #可以认为是.o文件
$< 相关文件 #可以认为是.c文件
.SUFFIXES:.c .o
CC=gcc
SRCS=hello.c
OBJS=$(SRCS:.c=.o)
EXEC=hello
start:$(OBJS)
@gcc -o $(EXEC) $<
@echo "----------OK"
.c.o:
@gcc -o $@ -c $<
clean:
@rm -rf $(OBJS) $(EXEC)
终极版本
一、含头文件
新建一个header.h
#ifndef HEAD_H
#define HEAD_H
void print();
#endif
和一个header.c
#include "header.h"
#include <stdio.h>
void print()
{
printf("In file Header.c\n");
}
在hello.c中包含header.h并且调用print()函数
#include <stdio.h>
#include "header.h"
int main()
{
printf("Hello world\n");
print();
return 0;
}
改写makefile如下:
.SUFFIXES:.c .o
CC=gcc
SRCS=hello.c\
header.c
OBJS=$(SRCS:.c=.o)
EXEC=hello
start:$(OBJS)
@gcc -o $(EXEC) $(SRCS)
@echo "----------OK"
.c.o:
@gcc -o $@ -c $<
clean:
@rm -rf $(OBJS) $(EXEC)
直接make,运行./hello,结果如下:
Hello world
In file Header.c
大功告成,最终版本就是这个,如果实在记不住怎么写,背过以后稍加改动就可以应用。
题外话
生成的.o文件不要随便一下全部删除,修改哪个.c文件删除响应的.o文件就可以了,因为删除所以.o编译器会重新编译一遍,造成不必要的时间浪费,make很智能,未修改的文件,make不会重新编译,他是通过最后修改时间来判断是否修改过,并且只关心.c和.o的时间差,不关心.h的时间差。
什么意思?就是说如果修改.h文件后,不删除.o文件,make不会去重新编译这个.c文件。