makefile文件
makefile的使用场景
makefile用于编译大量的.c文件 或者是分文件编写的(包含多个 .h 多个.c文件 和主文件 ) 将其转换为可执行文件 。 makefile的作用正是简化了上面的步骤,makefile的使用前提是掌握gcc 然后循序掌握其他内容
1 - 从简单的gcc 操作 到 转换为makefile
如我们要将 main.c 变为可执行文件 main
需要经历 gcc -E gcc -S gcc -C
当然对于单个.c文件转变为可执行文件 我们可以直接使用
gcc -C main.c -o main
在makefile中
main : main.c
gcc -c main.c -o main
main 是目标文件 ,main.c是依赖文件 下面必须用tab键 开头书写命令(若命令不存在的话则无需)
说明 : makefile中 目标文件一定要有 ,但依赖文件 和命令列表 可以没有
目标 :就是你要产生的文件,可以是 obj 文件
执行顺序
编写一个makefile 文件
binggo :test test2
echo "hello all"
test:
echo "hello test"
test2:
echo "hello test2"
makefile 默认会以文件中的第一个目标为最终目标 也就是 这里的 binggo, binggo 目标有两个依赖,test 和test2 .同样test 和test2 也作为目标 ,但他们不存在依赖文件。
执行顺序: 先执行依赖 test ,再执行依赖 test2 ,在依赖满足后 执行目标文件
可以使用 make makefile -n 来看一下执行过程
echo "hello test"
echo "hello test2"
echo "hello all"
再看多个.c文件编写的情况
提供 add.c mul.c sub.c add.h mul.h sub.h test.c 要求 将他们变为可执行文件
其中的函数形式为:
/*add.c*/
#include"add.h"
int add (int x,int y)
{
return x+y;
}
/*mul.c*/
#include"mul.h"
int mul (int x,int y)
{
return x*y;
}
/*sub.c*/
#include"sub.h"
int sub (int x,int y)
{
return x-y;
}
/*test.c*/
#include "add.h"
#include "sub.h"
#include "mul.h"
#include<stdio.h>
int main(void)
{
int x=2;
int y=4;
printf("%d",add(x,y));
printf("%d",sub(x,y));
printf("%d",mul(x,y));
printf("hello");
return 0;
}
/*add.h*/
#ifndef __ADD_H__
#define __ADD_H__
int add (int x,int y);
#endif /*__ADD_H__*/
/*mul.h*/
#ifndef __MUL_H__
#define __MUL_H__
int mul (int x,int y);
#endif /*__MUL_H__*/
/*sub.h*/
#ifndef __SUB_H__
#define __SUB_H__
int sub (int x,int y);
#endif /*__SUB_H__*/
使用gcc 的方式:
gcc add.c sub.c mul.c test.c -o test
说明:由于这里头文件和 多个c文件在同一个 目录下,所以不需要使用 -I 指定头文件
使用 makefile的方式
test: add.c sub.c mul.c test.c
gcc add.c sub.c mul.c test.c -o test
此时 目标文件test依赖 test: add.c sub.c mul.c test.c 所有c文件
首先确保程序已经执行一次(产生test可执行文件),然后我们对 add.c做一下修改(比如 使用touch add.c 更新一下时间戳)
然后再执行一下 make -n 执行顺序如下
gcc add.c sub.c mul.c test.c -o test
可见他会把所有的 .c文件全部在执行一次。
解决办法
当存在多个 c文件时,我们需要借助 c 文件生成的 obj文件(.o文件)
修改makefile
test:add.o sub.o mul.o test.o
gcc add.o sub.o mul.o test.o -o test
add.o:add.c
gcc -c add.c -o add.o
mul.o:mul.c
gcc -c mul.c -o mul.o
sub.o:sub.c
gcc -c sub.c -o sub.o
test.o:test.c
gcc -c test.c -o test.o
clean:
rm *.o
当我们的 add.c被修改之后, 目标 add.o 就会重新被执行 ,重新被执行的命令也就只有被修改的那部分
同样的我们在已经执行过一次的前提下,touch add.c ,表示add.c 被更新
然后使用 make -n展示编译过程
gcc -c add.c -o add.o
gcc add.o sub.o mul.o test.o -o test
(可见:我们使用.o 文件的目的是为了:当存在多个 . c文件时 , 我们若针对特定几个 .c文件 修改 ,make只会重新编译我们修改过后的那几个.c文件 ,并不会全部编译 ,这样就能节省时间 ,后续会谈到)
变量的介绍
变量是为了减少复杂代码的复写(方便程序引用)
定义变量
变量名=变量值
引用变量
( 变 量 名 ) 或 者 (变量名) 或者 (变量名)或者{变量名}
makefile的改进
OBJS=add.o sub.o mul.o test.o #test:add.o sub.o mul.o test.o注释掉
TARGET=test # gcc add.o sub.o mul.o test.o -o test
$(TARGET)=OBJS
gcc $(OBJS) -o $(TARGET)
add.o:add.c
gcc -c add.c -o add.o
mul.o:mul.c
gcc -c mul.c -o mul.o
sub.o:sub.c
gcc -c sub.c -o sub.o
test.o:test.c
gcc -c test.c -o test.o
clean:
rm -f $(OBJS) #rm *.o 注释掉
除了这些我们自己所定义的变量名 ,系统提供了一些变量提供给用户赋值,
-
CC = gcc #相当是gcc, 使用前 也要加 $(CC)
-
CPPFLAGS # c预处理的选项 -I 引用头文件等
-
CFLAGS # C编译器的选项 如-Wall -g -c 等
-
LDFLAGS #链接器的选项 -L -I 等
注明 : 这些只需要明白 他们只是是特定的名字 ,用来被一类的命令所赋值, 赋值前本身不作为任何原值
自动变量
- $@ 表示规则中的目标
- $< 表示规则中第一个依赖 如果将该变量应用在模式规则里,它可将依赖条件列表中的依赖依次取出,套用模式规则。
- $^ 表示规则中所有的依赖
使用自动变量我们可以进一步简化makefile 代码
OBJS=add.o sub.o mul.o test.o
TARGET=test
$(TARGET)=OBJS
gcc $^ -o $@
add.o:add.c
gcc -c $^ -o $@
mul.o:mul.c
gcc -c $^ -o $@
sub.o:sub.c
gcc -c $^ -o $@
test.o:test.c
gcc -c $^ -o $@
clean:
rm -f $(OBJS) $(TARGET)
我们发现 gcc -c $^ -o $@ 重复多次
模式匹配 形式如下
% .o : %.c
gcc -c $< -o %@
表示.o文件依赖 .c文件 ,也就是将 .c文件转换成 .o文件
这里的% 是通配符号 ,与 * 不同 ,在同一次使用中
比如 存在 add.c sub.c mul.c div.c
我们使用 %.o :%.c 这里表示将 以.c 为后缀的文件转换为 .o文件 但他每次就只能实现
add.c 转换为 add.o sub.c 转换为 sub.o div.c 转换为 div.o mul.c 转换为 mul.o
也就是说 .o的前缀 和 .c 的前缀是绑定的关系。
通配符 %的意思是
- 我要找test1.o的构造规则,看看Makefile中那个规则符合。
- 然后找到了%.o:%.c,
- 来套一下来套一下:
- %.o 和我要找的 test1.o 匹配
- 套上了,得到%=test1。
- 所以在后面的%.c就表示test1.c了。
- OK进行构造
而通配符*的意思是:
我不知道目标的名字,系统该目录下中所有后缀为.c的文件都是我要找的。
然后遍历目录的文件,看是否匹配。找出所有匹配的项目
针对上面的解释 修改一下makefile文件
OBJS=add.o sub.o mul.o test.o
TARGET=test
$(TARGET)=OBJS
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
说明 这里的 %.o %.c 依次会被 test.o sub.o add.o test.o 匹配到
当匹配到test.o 时
% =test
test.o:test.c
gcc -c test.c -o test.o
当匹配到add.o 时
% =add
add.o:add.c
gcc -c add.c -o add.o
如此下去,通配符是在带着目的(如“寻找test1.o”)的时候才会把他要寻找的目标套用通配符%中。
静态模式匹配 是为了指定这个目标采取哪种模式匹配(当存在多个模式匹配规则时)
<target>%.o%.c
gcc -c $< -o $A
比如: 新增add.s sub.s mul.s test.s
此时 .o文件的转换就不止是从 .c文件到 ,o 文件了 ,也可以是从 .s 文件到 .o文件
由于存在两种模式匹配规则,所以防止二义 加上<目标>
OBJS=add.o sub.o mul.o test.o
TARGET=test
$(TARGET)=OBJS
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
#%.o:%s #为使用静态匹配之前的
<OBJS>%.o:%.s #加上目标之后
gcc -S $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
两个函数与伪目标
观察上面改进的makefile 我们发现 我们仍然需要 写出所有的 .o文件
make 提供了 两个函数 $(wildcard ./*.c) 和 ( p a t s u b s t , (patsubst,%.c,%.o, (patsubst,(SRC))
$(wildcard ./*.c)
是获取指定目录下所有的以 c结尾的文件 (当然这里的路径可以自己改变)
( p a t s u b s t , (patsubst,%.c,%.o, (patsubst,(SRC)) 匹配替换
是将 SRC中所有出现 .c的文件替换为 .o
对此 将makefile 进一步修改
SRC=$(wildcard ./*.c)
OBJS= $(patsubst,%.c,%.o,$(SRC))
TARGET=test
$(TARGET)=OBJS
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
#%.o:%s #为使用静态匹配之前的
<OBJS>%.o:%.s #加上目标之后
gcc -S $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
伪目标
我们的makefile通常含有 目标 clean;这个目标没有依赖项
在 执行完成make 之后使用 ,用于清除中间文件 ( .o .test 最终文件 等)
倘若makefile 同级目录中存在同名clean文件则不会执行,
解决方案
伪目标声明 .PHONY:clean
声明目标为伪目标后,makefile将不会判断该目标是否存在,是否为最新的。
SRC=$(wildcard ./*.c)
OBJS= $(patsubst,%.c,%.o,$(SRC))
TARGET=test
$(TARGET)=OBJS
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
#%.o:%s #为使用静态匹配之前的
<OBJS>%.o:%.s #加上目标之后
gcc -S $< -o $@
.PHONY:clean
clean:
rm -f $(OBJS) $(TARGET)
clean 命令中的特殊符号 :
- ‘’ - ‘’ 此条命令出错,make 也会执行后续的命令
- “@ ” 不显示命令本身,只显示结果