C程序的编译通常分两步:
.c
文件编译为.o
文件;.o
文件链接为最终的可执行文件。我们假设如下的一个C项目,包含hello.c
、hello.h
和main.c
。
hello.c
内容如下:
#include <stdio.h>
int hello()
{
printf("hello, world!\n");
return 0;
}
hello.h
内容如下:
int hello();
main.c
内容如下:
#include <stdio.h>
#include "hello.h"
int main()
{
printf("start...\n");
hello();
printf("exit.\n");
return 0;
}
注意到main.c
引用了头文件hello.h
。我们很容易梳理出需要生成的文件,逻辑如下:
┌───────┐ ┌───────┐ ┌───────┐
│hello.c│ │main.c │ │hello.h│
└───────┘ └───────┘ └───────┘
│ │ │
│ └────┬────┘
│ │
▼ ▼
┌───────┐ ┌───────┐
│hello.o│ │main.o │
└───────┘ └───────┘
│ │
└───────┬──────┘
│
▼
┌─────────┐
│world.out│
└─────────┘
假定最终生成的可执行文件是world.out
,中间步骤还需要生成hello.o
和main.o
两个文件。根据上述依赖关系,我们可以很容易地写出Makefile
如下:
# 生成可执行文件:
world.out: hello.o main.o
cc -o world.out hello.o main.o
# 编译 hello.c:
hello.o: hello.c
cc -c hello.c
# 编译 main.c:
main.o: main.c hello.h
cc -c main.c
clean:
rm -f *.o world.out
执行make
,输出如下:
$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o
在当前目录下可以看到hello.o
、main.o
以及最终的可执行程序world.out
。执行world.out
:
$ ./world.out
start...
hello, world!
exit.
与我们预期相符。
修改hello.c
,把输出改为"hello, bob!\n"
,再执行make
,观察输出:
$ make
cc -c hello.c
cc -o world.out hello.o main.o
仅重新编译了hello.c
,并未编译main.c
。由于hello.o
已更新,所以,仍然要重新生成world.out
。执行world.out
:
$ ./world.out
start...
hello, bob!
exit.
与我们预期相符。
修改hello.h
:
// int 变为 void:
void hello();
以及hello.c
,再次执行make
:
$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o
会触发main.c
的编译,因为main.c
依赖hello.h
。
执行make clean
会删除所有的.o
文件,以及可执行文件world.out
,再次执行make
就会强制全量编译:
$ make clean && make
rm -f *.o world.out
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o
这个简单的Makefile
使我们能自动化编译C程序,十分方便。
不过,随着越来越多的.c
文件被添加进来,如何高效维护Makefile
的规则?我们后面继续讲解。
可以从GitHub下载源码。
在Makefile
正确定义规则后,我们就能用make
自动化编译C程序。