ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux(7) Makefile 入门:自动化构建 C/C++ 项目

Linux(7) Makefile 入门:自动化构建 C/C++ 项目 在 Linux 中我们可以直接使用gcc或g编译程序。只有一个源文件时手动输入编译命令并不麻烦gcc main.c-omain但随着项目逐渐变大源文件可能从一个增加到十几个甚至上百个。如果每次修改代码后都重新输入一长串编译命令不仅效率低还容易遗漏文件或写错参数。Makefile 就是用来解决这类问题的。我们把项目的构建规则写入 Makefile再通过make命令自动完成编译、链接和清理工作。一、make 与 Makefile 是什么make是一个自动化构建工具Makefile 是保存构建规则的文本文件。二者的关系可以简单理解为Makefile 告诉系统“要生成什么、依赖什么、应该执行什么命令”make读取这些规则判断哪些文件需要重新生成然后执行相应命令。Makefile 的常见文件名有Makefile makefile通常推荐使用Makefile。进入文件所在目录后直接执行makemake就会自动查找并读取 Makefile。二、第一份 Makefile准备一个简单的 C 程序main.c#includestdio.hintmain(void){printf(hello Makefile\n);return0;}在同一目录下创建Makefilemain: main.c gcc main.c -o main然后执行make屏幕上会显示实际执行的编译命令并生成可执行文件main。运行程序./main输出hello Makefile三、Makefile 的基本规则一条最基本的 Makefile 规则由三部分组成目标文件: 依赖文件 生成目标文件需要执行的命令对应到前面的例子main: main.c gcc main.c -o main其中main是目标即最终希望生成的文件main.c是依赖生成main需要使用它gcc main.c -o main是命令描述如何生成目标。需要特别注意命令行开头必须是Tab 键不能直接用普通空格代替。缩进不正确时经常会出现下面的错误Makefile:2: *** missing separator. Stop.遇到这个错误时应首先检查命令前面是否使用了 Tab。四、make 为什么不会每次都重新编译第一次执行make后再次执行make可能会看到make: main is up to date.这是因为make会比较目标文件和依赖文件的最后修改时间如果目标不存在就执行命令生成目标如果依赖比目标更新说明源代码发生了变化需要重新生成目标如果目标比依赖更新说明目标已经是最新状态不需要重复构建。可以修改一下main.c或者使用下面的命令更新时间touchmain.cmake此时main.c的修改时间晚于mainmake就会重新执行编译命令。这就是 Makefile 增量构建的基础。五、把编译和链接分开C/C 程序的构建通常不只是一步。源文件先被编译为目标文件目标文件再经过链接生成可执行程序。例如main: main.o gcc main.o -o main main.o: main.c gcc -c main.c -o main.o构建过程如下main.c --编译-- main.o --链接-- main执行make时它发现main依赖main.o而main.o又依赖main.c于是会按照依赖关系自动确定构建顺序。这里用到的-c选项表示只进行编译和汇编暂时不链接gcc-cmain.c-omain.o六、多文件项目示例假设项目中有三个文件project/ ├── Makefile ├── main.c ├── add.c └── add.hadd.h#ifndefADD_H#defineADD_Hintadd(intx,inty);#endifadd.c#includeadd.hintadd(intx,inty){returnxy;}main.c#includestdio.h#includeadd.hintmain(void){printf(10 20 %d\n,add(10,20));return0;}对应的 Makefileapp: main.o add.o gcc main.o add.o -o app main.o: main.c add.h gcc -c main.c -o main.o add.o: add.c add.h gcc -c add.c -o add.o执行make./app输出10 20 30如果只修改add.c再次执行make时只需要重新生成add.o然后重新链接appmain.o不会重复编译。这能显著减少大型项目的构建时间。七、使用变量简化 Makefile上面的 Makefile 多次出现gcc、目标文件名和编译参数。可以使用变量减少重复内容CC gcc TARGET app OBJS main.o add.o $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) main.o: main.c add.h $(CC) -c main.c -o main.o add.o: add.c add.h $(CC) -c add.c -o add.o变量通过下面的形式定义变量名 变量值引用变量时使用$(变量名)常见变量命名包括CCC 编译器CXXC 编译器CFLAGSC 编译选项CXXFLAGSC 编译选项LDFLAGS链接选项TARGET最终目标名称OBJS目标文件列表。例如加入常用警告和调试选项CC gcc CFLAGS -Wall -Wextra -g TARGET app OBJS main.o add.o $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) main.o: main.c add.h $(CC) $(CFLAGS) -c main.c -o main.o add.o: add.c add.h $(CC) $(CFLAGS) -c add.c -o add.o这样修改编译器或编译参数时只需要修改变量定义。八、自动变量Makefile 提供了一些自动变量可以表示当前规则中的目标和依赖。常用自动变量有自动变量含义$当前规则的目标文件$当前规则的第一个依赖文件$^当前规则的全部依赖文件重复项会被去除例如CC gcc CFLAGS -Wall -Wextra -g TARGET app OBJS main.o add.o $(TARGET): $(OBJS) $(CC) $^ -o $ main.o: main.c add.h $(CC) $(CFLAGS) -c $ -o $ add.o: add.c add.h $(CC) $(CFLAGS) -c $ -o $在链接规则中$表示app$^表示main.o add.o。在main.o的规则中$表示main.o$表示第一个依赖main.c。自动变量能让规则更加通用也能减少手写文件名产生的错误。九、模式规则main.o和add.o的编译规则非常相似可以使用模式规则合并%.o: %.c $(CC) $(CFLAGS) -c $ -o $其中%表示相同的文件名部分main.o对应main.cadd.o对应add.c。整理后的 MakefileCC gcc CFLAGS -Wall -Wextra -g TARGET app OBJS main.o add.o $(TARGET): $(OBJS) $(CC) $^ -o $ main.o: main.c add.h add.o: add.c add.h %.o: %.c $(CC) $(CFLAGS) -c $ -o $这里单独写出头文件依赖是为了让add.h修改后相关目标文件能够重新编译。十、clean 与伪目标项目开发过程中经常需要删除编译产生的目标文件和可执行文件。可以添加clean规则clean: rm -f $(OBJS) $(TARGET)执行makecleanclean并不是需要生成的真实文件而是一项希望执行的操作。为了防止目录中恰好存在一个名为clean的文件通常将它声明为伪目标.PHONY: clean完整写法CC gcc CFLAGS -Wall -Wextra -g TARGET app OBJS main.o add.o $(TARGET): $(OBJS) $(CC) $^ -o $ main.o: main.c add.h add.o: add.c add.h %.o: %.c $(CC) $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(OBJS) $(TARGET)还可以增加all伪目标.PHONY: all clean all: $(TARGET)Makefile 默认执行第一个目标因此如果希望make默认构建全部内容可以把all放在规则区域的最前面。十一、指定目标执行如果 Makefile 中存在多个目标可以在make后面指定要执行的目标makeallmakeappmakeclean如果不写目标makemake默认构建 Makefile 中出现的第一个目标。还可以使用-f指定其他文件名make-fMyMakefile十二、常见问题1. missing separator错误示例*** missing separator. Stop.通常是命令行前没有使用 Tab或者 Makefile 格式不正确。2. No rule to make target错误示例make: *** No rule to make target add.o, needed by app. Stop.常见原因包括依赖文件名写错对应源文件不存在没有提供生成该目标的规则。3. undefined reference链接阶段出现undefined reference to add说明链接器没有找到函数实现。应检查实现该函数的源文件是否已经编译对应的.o文件是否参与链接函数声明和定义是否一致所需的静态库或动态库是否正确加入链接命令。4. 修改头文件后没有重新编译如果规则只写main.o: main.c那么修改add.h后make不知道main.o也依赖这个头文件。应正确补充依赖main.o: main.c add.h十三、总结Makefile 的核心并不是记住大量语法而是理解“目标、依赖、命令”之间的关系。make读取 Makefile 并自动执行构建规则规则基本格式是“目标依赖”下一行以 Tab 开头书写命令make根据文件修改时间决定是否重新构建变量可以统一管理编译器、编译选项和文件列表$、$、$^等自动变量可以简化规则模式规则适合处理重复的编译过程.PHONY用于声明clean、all等伪目标多文件项目中应准确描述源文件、头文件和目标文件之间的依赖。掌握这些基础内容后就能用 Makefile 管理普通的 C/C 项目并为后续学习更复杂的构建系统打下基础。
返回列表