# 编写Makefile

**源代码介绍**

在本教程中用于示例的代码很简单，仅仅是在main函数中调用了fun1及fun2函数，而fun1及fun2独立写在fun1.c及fun2.c里。代码如下：

```python
//main.c  
int main()  
{  
    printf("hello world\n");  
    fun1();  
    fun2();  
}  
//fun1.c  
void fun1()  
{  
    printf("this is fun1\n");  
}  
//fun2.c  
void fun2()  
{  
    printf("this is fun2\n");  
}
```

**第一版Makefile**

对于我们的示例代码，不通过Makefile编译其实也很简单：

```
gcc main.c fun1.c fun2.c -o app
```

我们知道，Makefile其实就是按规则一条条的执行。所以，我们完全可以把上面那条命令写成Makefile的一个规则。我们的目标是app，按此写法依赖是main.c fun1.c fun2.c，则最终的Makefile如下：

```
app: main.c fun1.c fun2.c  
    gcc main.c fun1.c fun2.c -o app
```

但这个版本的Makefile有两个很重要的不足：

1. 对于简单代码还好，而对于大型项目，具有成千上万代码来说，仅用一行规则是完全不够的，即使够的话也需要写很长的一条规则；
2. 任何文件只要稍微做了修改就需要整个项目完整的重要编译。

基于此，我们在第一版的基础上优化出第二版。

**4. 第二版Makefile**

在第二版Makefile中，为了避免改动任何代码就需要重新编译整个项目的问题，我们将主规则的各个依赖替换成各自的中间文件，即main.c --> main.o，fun1.c --> fun1.o，fun2.c --> fun2.o，再对每个中间文件的生成各自写条规则比如对于main.o，规则为：

```
main.o: main.c  
    gcc -c main.c -o main.o
```

这样做的好处是，当有一个文件发生改动时，只需重新编译此文件即可，而无需重新编译整个项目。完整Makefile如下：

```
app: main.o fun1.o fun2.o  
    gcc main.o fun1.o fun2.o -o app  

main.o: main.c  
    gcc -c main.c -o main.o  

fun1.o: fun1.c  
    gcc -c fun1.c -o fun1.o  

fun2.o: fun2.c  
    gcc -c fun2.c -o fun2.o
```

第二版Makefile同样具有一些缺陷：

1. 里面存在一些重复的内容，可以考虑用变量代替；
2. 后面三条规则非常类似，可以考虑用一条模式规则代替。

基于此，我们在第二版的基础上优化出第三版。

**5. 第三版Makefile**

在第三版Makefile中，我们使用变量及模式规则使Makefile更加简洁。使用的三个变量如下：

```
obj = main.o fun1.o fun2.o  
target = app  
CC = gcc
```

使用的模式规则为：

```
%.o: %.c  
     $(CC) -c $< -o $@
```

这条模式规则表示：所有的.o文件都由对应的.c文件生成。在规则里，我们又看到了两个自动变量：$<和$@。其实自动变量有很多，常用的有三个：

​ $<：第一个依赖文件；

​ $@：目标；

​ $^：所有不重复的依赖文件，以空格分开

```
obj = main.o fun1.o fun2.o  
target = app  
CC = gcc  

$(target): $(obj)  
    $(CC) $(obj) -o $(target)  

%.o: %.c  
    $(CC) -c $< -o $@
```

第三版Makefile依然存在一些缺陷：

1. obj对应的文件需要一个个输入，工作量大；
2. 文件数目比较少时还好，文件数目一旦很多的话，obj将很长；
3. 而且每增加/删除一个文件，都需要修改Makefile。

基于此，我们在第二版的基础上优化出第四版。

**6. 第四版Makefile**

在第四版Makefile中，我们隆重推出了两个函数：**wildcard**和**patsubst**。

**wildcard：**

扩展通配符，搜索指定文件。在此我们使用src = $(wildcard ./\*.c)，代表在当前目录下搜索所有的.c文件，并赋值给src。函数执行结束后，src的值为：main.c fun1.c fun2.c。

**patsubst：**

替换通配符，按指定规则做替换。在此我们使用obj = $(patsubst %.c, %.o, $(src))，代表将src里的每个文件都由.c替换成.o。函数执行结束后，obj的值为main.o fun1.o fun2.o，其实跟第三版Makefile的obj值一模一样，只不过在这里它更智能一些，也更灵活。

除了使用patsubst函数外，我们也可以使用模式规则达到同样的效果，比如：obj = $(src:%.c=%.o)，也是代表将src里的每个文件都由.c替换成.o。

几乎每个Makefile里都会有一个伪目标clean，这样我们通过执行make clean命令就是将中间文件如.o文件及目标文件全部删除，留下干净的空间。一般是如下写法：

```
.PHONY: clean  
clean:  
        rm -rf $(obj) $(target)
```

.PHONY代表声明clean是一个伪目标，这样每次执行make clean时，下面的规则都会被执行。

```
src = $(wildcard ./*.c)  
obj = $(patsubst %.c, %.o, $(src))  
#obj = $(src:%.c=%.o)  
target = app  
CC = gcc  

$(target): $(obj)  
    $(CC) $(obj) -o $(target)  

%.o: %.c  
    $(CC) -c $< -o $@  

.PHONY: clean  
clean:  
    rm -rf $(obj) $(target)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://im-qianuxn.gitbook.io/pytorch/ji-suan-ji/shell-linux/bian-xie-make-file.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
