makefile简单初探索
系统 ubuntu20.04
参考正点原子
只是学习中自己的小记
0.引子
我们常常会遇到有c编译的需求,在linux环境下一般使用gcc编译,如
gcc main.c -o main
如果还有引用别的文件,则
gcc main.c file1.c file2.c -o main
这在仅有一个文件或少量文件时,是非常方便使用的。
但是这里有两个问题:
– 文件不可能一直这么少,随着工程的增大,文件只会越来越多
这个时候你总不能还在
gcc后面一直加文件吧
– 当文件多起来后,编译也很费时,总不能每次都重新编译吧
这个时候链接就好用多了
这可不好!需要想办法解决!
对此,就轮到 make 大展身手啦!
1. 从小案例开始
1.1 准备工作
这里,我们先写个很乐色的代码
/* main.c */
#include "io.h"
#include "calcu.h"
int main(void)
{
int num1, num2;
int answer;
get_two_number(&num1,&num2);
answer = my_add(num1,num2);
display_number(&answer);
return 0;
}
calcu.c
/* calcu.c */
#include "calcu.h"
int my_add(int num1, int num2)
{
return num1 + num2;
}
io.c
/* io.c */
#include "io.h"
void get_two_number(int* pnum1, int* pnum2)
{
printf("input 2 number here:\n");
scanf("%d%d", pnum1,pnum2);
}
void display_number(int* pnum)
{
printf("%d\n",*pnum);
}
calcu.h
/* calcu.h */
#ifndef __CALCU_H__
#define __CALCU_H__
int my_add(int,int);
#endif
io.h
/* io.h*/
#ifndef __MYIO_H__
#define __MYIO_H__
#include <stdio.h>
void get_two_number(int*,int*);
void display_number(int*);
#endif
1.2 最基础的makefile
首先我们先介绍makefile的基础规则
makefile可以拆成如下的基础块
target: dependence
command1
command2
...
其中,target是你的目标,dependence是你的依赖项。
当你执行make target(这里更换为实际target)时,make工具会检查dependence是否存在或满足。如果满足,则会执行命令command。
version 0
接下来,我们将一步一步改造我们的makefile,这里有几点提醒要注意:
– 缩进要是Tab,但是某些平台会将Tab转换为4个空格,这将会导致makefile报错!
version 1
对此,我们上述的代码用make来实现,就应该是
main: main.c io.c calcu.c
gcc main.c io.c calcu.c -o main
clean:
rm main
rm *.o
version 2
但是这样解决不了问题2,这实际上还是全编译。
对此,我们要把它拆开
main: main.o io.o calcu.o
gcc main.o io.o calcu.o -o main
main.o: main.c
gcc -c main.c
calcu.o: calcu.c
gcc -c calcu.c
io.o: io.c
gcc -c io.c
clean:
rm main
rm *.o
很好,现在编译就不是全编译了。
version 3
不过这样的编写太冗长了,如main.o io.o calcu.o重复写就很烦。
这个时候,我们就可以使用变量
objects = main.o io.o calcu.o
main: (objects)
gcc(objects) -o main
main.o: main.c
gcc -c main.c
calcu.o: calcu.c
gcc -c calcu.c
io.o: io.c
gcc -c io.c
clean:
rm main
rm *.o
version 4
上面的代码虽然已经足够简洁了,但是一个一个的写 gcc 编译还是有些烦。
此时,通配符就出马了。
objects = main.o io.o calcu.o
main: (objects)
gcc(objects) -o main
%.o: %.c
gcc -c $<
clean:
rm main
rm *.o
其中%表示任意文字,%.o: %.c 表示:任何 .o 文件都可以由同名的 .c 文件生成。(在dependence里的%具体是什么内容,取决于执行此规则时候的target中的%是什么内容)
在command中的$<,表示第一个依赖项(如果有多个)。
version 5
上文已经足够简单,但是忘记考虑了一个东西,即依赖项没有添加头文件,因为实际工程中,头文件又不是不会修改。
makefile会递归检查所有依赖的时间戳。
objects = main.o io.o calcu.o
main: (objects)
gcc(objects) -o main
%.o: %.c %.h
gcc -c $<
clean:
rm main
rm *.o
version 6
如果你尝试在同文件夹下面添加一个clean文件,你再次尝试清理make clean时会发现出现
make: 'clean' is up to date.
这是因为clean没有任何依赖,而且clean已经存在,所以导致的问题。
对此,可以使用伪目标来避免。
objects = main.o io.o calcu.o
main: (objects)
gcc(objects) -o main
%.o: %.c %.h
gcc -c $<
.PHONY = clean
clean:
rm main
rm *.o
其中.PHONY : clean就是在说明target clean只是一个伪目标,不会真的生成东西。
2 小结
至此,我们一个小型的makefile已经写完了。
但是很显然,很多makefile的知识点都没有提及。
如
– 更详细的通配符介绍
– 条件语句的介绍
– 函数的介绍
剩下的这些,可以在实际工程中边学便摸索。
不要一味追求完美,尤其在面对陌生领域时。
先动手,再优化——你越早使用工具,就越快掌握它;
越快掌握它,就越高效地推进项目,甚至真正踏入这片未知之地。
所以,先动手吧。
3. 参考资料
[1] 正点原子资料
[2] 《跟我一起写makefile》