一、前言
在之前的章节中我们提到,为了隐藏源码,可以将.c 文件编译后发送。但如果源文件过多,分发和使用都很麻烦。本章将解决该问题。
1.1 补充知识
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中。Makefile 定义了系列的规则来指定哪些文件需要先编译,哪些需要后编译,哪些需要重新编译,甚至进行更复杂的功能操作。Makefile 带来的好处是'自动化编译',一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大地提高了软件开发的效率。
Make 是一个命令工具,是解释 Makefile 中指令的命令工具。Makefile 内部包含依赖关系和依赖方式。
二、理解
我们先创建一个 Makefile 文件(大小写均可)和 myproc.c 文件。
myproc.c
Makefile
然后运行 make,即可编译成功,再进行输出。
注意:项目是要被清理的,所以把 clean 的目标文件设置为伪目标(后续再讲)
上面的 Makefile 文件中:
- 依赖关系:上面的文件 myproc,它依赖 myproc.c。
- 依赖方式:gcc -o myproc myproc.c,就是与之对应的依赖关系。
2.1 引入小故事:加强理解
月底没生活费了,你打电话给家里人(你和家里人的关系就是依赖关系),问他们寻求支助生活费(这就是依赖方式)。
注:依赖关系与依赖方式必须对得上才有用(你不可能打电话问室友的爸爸要生活费)。
2.2 伪目标
项目工程是需要被 clean 的,一般我们这种 clean 的目标文件,我们将它设置为伪目标,用 .PHONY 修饰。伪目标的特性是总是被执行的。
即似乎 myproc 没有伪目标,就不会总是被执行。
[lcb@hcss-ecs-1cde 1]$ make gcc -o myproc myproc.c
[lcb@hcss-ecs-1cde 1]$ make
make: `myproc' is up to date.
[lcb@hcss-ecs-1cde 1]$ make clean
rm -f myproc
[lcb@hcss-ecs-1cde 1]$ make clean
rm -f myproc
[lcb@hcss-ecs-1cde 1]$
上面也确实如此。
注:编译器编译时是自上而下,也就是说如果把 clean 放在最前面,那 make 执行的就是 clean,而不是 myproc。
同样,如果我们为 myproc 加上.PHONY 呢,事实就是也可以无限次执行。
可以如此的原因是:编译器默认老代码不再编译。
2.3 Make 如果知道 bin 和.c 的新旧关系
Make 通过 Modify 知道 bin 和.c 的新旧关系。
此处就需要回顾前面提到的时间戳及文件组成:内容 + 属性。
- Modify:内容变更,时间更新。
- Change:属性变更,时间更新。
- Access (特殊):常指的是文件最近一次被访问的时间。在 Linux 的早期版本中,每当文件被访问时,其 atime 都会更新。但这种机制会导致大量的 IO 操作。具体更新原则,不做过多解释。
注意,如果修改内容,属于也变更(修改内容很有可能会更新大小,大小属于属性)。
此时如果再进行 make 执行,由于 bin 的时间比.c 文件新,所以.c 为老代码,不再编译。
此时如果进行修改,改变.c 的时间戳,那么.c 文件与 myproc 相比,就为新了,可进行修改,那么就可以执行文件,进行编译。
2.4 继续手动编译过程:加强理解
在上一章我们已经详细讲述了编译过程,此处不再解释,只是给出代码。
myproc:myproc.o
gcc myproc.o -o myproc
myproc.o:myproc.s
gcc -c myproc.s -o myproc.o
myproc.s:myproc.i
gcc -S myproc.i -o myproc.s
myproc.i:myproc.c
gcc -E myproc.c -o myproc.i
.PHONY:clean
clean:
rm -f *.i *.s *.o myproc
上面识别过程由上到下压栈,要是符合,再由下到上出栈。
2.5 适度扩展语法:解决仍存在的多文件繁琐问题
N=proc.exe
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
LFLAGS=-o
FLAGS=-c
RM=rm -rf
# 2. 链接规则
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^
@echo "linking ... $^ to $@"
# 3. 编译规则(.c → .o,批量编译)
%.o:%.c
@$(CC) $(FLAGS) $< -o $@
@echo "compiling ... $< to $@"
# 4. 清理伪目标(修正 RM 变量后可正常删除文件)
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
# 5. 测试伪目标(修正执行命令,逻辑合理化)
.PHONY: test
test: $(BIN)
@echo "=== Source files: $(SRC) ==="
@echo "=== Object files: $(OBJ) ==="
@./$(BIN)
注:上面的 SRC 也可改为
SRC=$(shell ls *.c)。
三、实战:利用 make Makefile 制作进度条
3.1 版本一
核心代码 main.c 和 process.c。
但上面代码有所缺陷,即 download 定义的太多死板,后面更新上传又要重新传参,因此我们可以使用 C 语言学习的知识,回调函数。
3.2 版本二
修改的核心代码:
#include <stdio.h>
#include "process.h"
typedef void (*callback)(double total, double current);
double total = 1024.0;
double speed = 1.0;
void Download(callback cb) {
double current = 0.0;
while(current < total) {
cb(total, current);
usleep(3000);
current += speed;
}
printf("\ndownload%.2fMB done\n", current);
}
int main() {
// 定义回调函数处理进度显示
Download(print_progress);
return 0;
}
上面的修改即可解决参数传递繁琐的问题。


