ARTICLE DETAIL

资讯详情

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

从零掌握Makefile:C/C++项目构建自动化与工程化实践

从零掌握Makefile:C/C++项目构建自动化与工程化实践 如果你在C语言项目中遇到过这些问题每次修改代码都要手动敲一堆gcc命令不同源文件之间的依赖关系理不清或者团队协作时每个人的编译环境都不一样导致“在我机器上能跑”的尴尬——那么你需要的不是更复杂的IDE而是一个真正理解Makefile。Makefile不是“可选技能”而是C/C开发者的工程化分水岭。很多人以为它只是把编译命令写进一个文件但实际上一个设计良好的Makefile能帮你管理依赖、自动化测试、区分开发与发布版本甚至集成静态分析工具。更重要的是它让你从“写代码的人”变成“管理项目的人”。网上很多Makefile教程要么太浅只教几个规则要么太深直接搬GNU Make手册。本文的目标是用80分钟让你从零写出一个能用于真实项目的Makefile。我们不只讲语法更会解释每个设计选择背后的工程考量以及那些新手最容易踩的坑。1. 为什么你的C项目需要一个Makefile在讨论语法之前我们先明确Makefile到底解决了什么实际问题。1.1 手动编译的痛点假设你有一个简单的项目包含三个文件// main.c #include stdio.h #include math_utils.h int main() { int a 10, b 5; printf(add(%d, %d) %d\n, a, b, add(a, b)); printf(sub(%d, %d) %d\n, a, b, sub(a, b)); return 0; }// math_utils.h #ifndef MATH_UTILS_H #define MATH_UTILS_H int add(int a, int b); int sub(int a, int b); #endif// math_utils.c #include math_utils.h int add(int a, int b) { return a b; } int sub(int a, int b) { return a - b; }没有Makefile时你可能这样编译gcc -c math_utils.c -o math_utils.o gcc -c main.c -o main.o gcc main.o math_utils.o -o myapp每次修改都要重新敲一遍。如果文件多了命令会更复杂而且容易出错。1.2 Makefile带来的改变同样的项目有了Makefile后make # 编译所有 make clean # 清理中间文件 make run # 编译并运行 make debug # 用调试选项编译更重要的是Makefile能增量编译只重新编译修改过的文件依赖管理自动处理头文件修改环境统一团队每个人用同样的编译选项任务自动化集成测试、打包、文档生成1.3 什么时候需要Makefile项目超过3个源文件需要链接外部库要区分调试版和发布版需要自动化测试或部署团队协作开发如果你还在用IDE的“一键编译”那么你其实把项目的构建逻辑交给了黑盒。Makefile让你重新掌握控制权。2. Makefile基础从Hello World到真实项目2.1 最简单的Makefile创建一个名为Makefile的文件注意首字母大写没有扩展名# 最简单的Makefile示例 hello: echo Hello, Makefile!运行$ make hello echo Hello, Makefile! Hello, Makefile!这里有几个关键点hello:定义了一个目标target下一行必须以Tab开头不能是空格echo是要执行的命令默认会显示执行的命令前面加可以隐藏2.2 第一个真正的编译Makefile回到我们的数学工具项目创建第一个实用的Makefile# 定义编译器 CC gcc # 定义编译选项 CFLAGS -Wall -Wextra -stdc99 # 定义目标可执行文件 TARGET myapp # 定义源文件 SRCS main.c math_utils.c # 生成对应的目标文件列表 OBJS $(SRCS:.c.o) # 默认目标编译所有 all: $(TARGET) # 链接目标文件生成可执行文件 $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $ $^ # 编译.c文件为.o文件 %.o: %.c $(CC) $(CFLAGS) -c $ -o $ # 清理生成的文件 clean: rm -f $(OBJS) $(TARGET) # 运行程序 run: $(TARGET) ./$(TARGET) # 伪目标声明防止有同名文件时出错 .PHONY: all clean run2.3 关键语法解释变量定义CC gcc定义变量用$(CC)引用自动变量$目标文件名$第一个依赖文件名$^所有依赖文件$?比目标新的依赖文件模式规则%.o: %.c匹配所有.c文件生成.o文件伪目标.PHONY声明不是真实文件的目标3. 环境准备与工具选择3.1 安装Make工具Linux/macOS通常已安装Windows需要安装# Ubuntu/Debian sudo apt-get install make # CentOS/RHEL sudo yum install make # macOS (使用Homebrew) brew install make # Windows (使用MinGW或Cygwin) # 或者使用WSL3.2 检查Make版本$ make --version GNU Make 4.3 Built for x86_64-pc-linux-gnu3.3 推荐的工具链编译器gcc 或 clang调试器gdb静态分析cppcheck代码检查valgrind内存检查格式化工具clang-format文档生成doxygen3.4 项目目录结构建议my_project/ ├── Makefile ├── README.md ├── src/ │ ├── main.c │ ├── math_utils.c │ └── math_utils.h ├── build/ # 编译输出目录 ├── tests/ # 测试代码 ├── docs/ # 文档 └── lib/ # 第三方库4. 进阶Makefile编写技巧4.1 自动依赖生成头文件修改时需要重新编译依赖它的源文件。手动维护这种依赖很痛苦可以自动生成# 生成依赖文件 DEPS $(SRCS:.c.d) # 包含依赖文件 -include $(DEPS) # 生成.d文件包含头文件依赖 %.d: %.c $(CC) -MM $ -MT $ $(:.d.o) $这样修改头文件后依赖它的源文件会自动重新编译。4.2 区分调试版和发布版# 根据BUILD_TYPE选择编译选项 BUILD_TYPE ? debug ifeq ($(BUILD_TYPE), debug) CFLAGS -g -O0 -DDEBUG TARGET : $(TARGET)_debug else ifeq ($(BUILD_TYPE), release) CFLAGS -O2 -DNDEBUG TARGET : $(TARGET)_release else $(error BUILD_TYPE must be debug or release) endif使用make BUILD_TYPEdebug # 编译调试版 make BUILD_TYPErelease # 编译发布版4.3 支持多平台编译# 检测操作系统 UNAME_S : $(shell uname -s) # 平台特定设置 ifeq ($(UNAME_S), Linux) # Linux设置 CFLAGS -D LINUX LDFLAGS -lm -lpthread else ifeq ($(UNAME_S), Darwin) # macOS设置 CFLAGS -D MACOS LDFLAGS -framework CoreFoundation else ifeq ($(UNAME_S), Windows_NT) # Windows设置 CFLAGS -D WINDOWS LDFLAGS -lws2_32 endif4.4 颜色输出支持让Makefile的输出更易读# 颜色定义 RED : \033[0;31m GREEN : \033[0;32m YELLOW : \033[0;33m BLUE : \033[0;34m NC : \033[0m # No Color # 带颜色的信息输出 INFO echo $(GREEN)[INFO]$(NC) $1 WARN echo $(YELLOW)[WARN]$(NC) $1 ERROR echo $(RED)[ERROR]$(NC) $1 # 使用示例 all: $(TARGET) $(call INFO, Build completed: $(TARGET))5. 完整项目示例一个实用的C项目Makefile下面是一个接近真实项目的Makefile包含了最佳实践# # 项目配置 # # 项目名称 PROJECT_NAME myapp # 构建类型debug 或 release BUILD_TYPE ? debug # 安装前缀用于make install PREFIX ? /usr/local # # 工具链配置 # # 编译器 CC gcc # 链接器 LD $(CC) # 归档工具用于静态库 AR ar ARFLAGS rcs # # 目录配置 # # 源代码目录 SRC_DIR src # 头文件目录 INC_DIR include # 构建目录 BUILD_DIR build # 测试目录 TEST_DIR tests # 库目录 LIB_DIR lib # # 文件查找 # # 查找所有.c文件 SRCS $(shell find $(SRC_DIR) -name *.c) # 查找所有测试.c文件 TEST_SRCS $(shell find $(TEST_DIR) -name *.c) # 生成对应的.o文件 OBJS $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS)) # 生成对应的.d文件依赖文件 DEPS $(OBJS:.o.d) # 测试目标文件 TEST_OBJS $(patsubst $(TEST_DIR)/%.c, $(BUILD_DIR)/%.o, $(TEST_SRCS)) # # 编译选项 # # 基础警告选项 WARN_FLAGS -Wall -Wextra -Wpedantic \ -Wformat2 -Wno-unused-parameter \ -Wshadow -Wwrite-strings -Wstrict-prototypes \ -Wold-style-definition -Wredundant-decls \ -Wnested-externs -Wmissing-include-dirs # 根据构建类型设置优化选项 ifeq ($(BUILD_TYPE), debug) # 调试版本 OPT_FLAGS -g -O0 -DDEBUG TARGET $(BUILD_DIR)/$(PROJECT_NAME)_debug else ifeq ($(BUILD_TYPE), release) # 发布版本 OPT_FLAGS -O2 -DNDEBUG TARGET $(BUILD_DIR)/$(PROJECT_NAME)_release else $(error BUILD_TYPE must be debug or release) endif # 包含目录 INC_FLAGS -I$(INC_DIR) -I$(SRC_DIR) # 编译标志 CFLAGS $(WARN_FLAGS) $(OPT_FLAGS) $(INC_FLAGS) -stdc99 # 链接标志 LDFLAGS -lm # # 测试配置 # # 测试框架这里以Check为例需要先安装 CHECK_CFLAGS $(shell pkg-config --cflags check) CHECK_LDFLAGS $(shell pkg-config --libs check) # 测试可执行文件 TEST_TARGET $(BUILD_DIR)/test_runner # # 构建规则 # # 默认目标 all: $(TARGET) # 主目标链接所有目标文件 $(TARGET): $(OBJS) mkdir -p $(dir $) $(LD) $^ -o $ $(LDFLAGS) echo Build completed: $ # 编译.c文件为.o文件 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c mkdir -p $(dir $) $(CC) $(CFLAGS) -c $ -o $ # 生成依赖文件 $(BUILD_DIR)/%.d: $(SRC_DIR)/%.c mkdir -p $(dir $) $(CC) $(CFLAGS) -MM -MT $ $(:.d.o) $ $ # 包含依赖文件 -include $(DEPS) # # 测试规则 # # 构建并运行测试 test: $(TEST_TARGET) echo Running tests... ./$(TEST_TARGET) # 编译测试 $(TEST_TARGET): $(filter-out $(BUILD_DIR)/main.o, $(OBJS)) $(TEST_OBJS) mkdir -p $(dir $) $(LD) $^ -o $ $(LDFLAGS) $(CHECK_LDFLAGS) # 编译测试文件 $(BUILD_DIR)/%.o: $(TEST_DIR)/%.c mkdir -p $(dir $) $(CC) $(CFLAGS) $(CHECK_CFLAGS) -c $ -o $ # # 实用目标 # # 运行程序 run: $(TARGET) ./$(TARGET) # 清理构建文件 clean: rm -rf $(BUILD_DIR) echo Cleaned build directory # 安装到系统 install: $(TARGET) install -d $(PREFIX)/bin install -m 755 $(TARGET) $(PREFIX)/bin/$(PROJECT_NAME) echo Installed to $(PREFIX)/bin/$(PROJECT_NAME) # 卸载 uninstall: rm -f $(PREFIX)/bin/$(PROJECT_NAME) echo Uninstalled from $(PREFIX)/bin/$(PROJECT_NAME) # 生成tags文件用于代码导航 tags: ctags -R . # 代码格式检查 lint: cppcheck --enableall --suppressmissingIncludeSystem $(SRC_DIR) # 内存检查需要valgrind memcheck: $(TARGET) valgrind --leak-checkfull --show-leak-kindsall ./$(TARGET) # # 伪目标声明 # .PHONY: all clean run test install uninstall tags lint memcheck # # 帮助信息 # help: echo Available targets: echo all - 构建项目默认 echo debug - 构建调试版本make BUILD_TYPEdebug echo release - 构建发布版本make BUILD_TYPErelease echo clean - 清理构建文件 echo run - 构建并运行 echo test - 构建并运行测试 echo install - 安装到系统默认/usr/local/bin echo uninstall - 卸载 echo tags - 生成tags文件 echo lint - 代码静态检查 echo memcheck - 内存检查需要valgrind echo help - 显示此帮助信息 echo echo 示例 echo make # 构建调试版本 echo make BUILD_TYPErelease # 构建发布版本 echo make clean # 清理 echo make run # 构建并运行5.1 项目结构示例配合上面的Makefile项目结构应该是my_project/ ├── Makefile ├── README.md ├── .gitignore ├── src/ │ ├── main.c │ ├── math_utils.c │ ├── math_utils.h │ └── other_module.c ├── include/ │ └── project.h ├── tests/ │ ├── test_math.c │ └── test_other.c ├── lib/ │ └── third_party_lib.a └── docs/ └── api.md5.2 关键特性说明自动文件查找使用find命令自动发现源文件分离构建目录所有中间文件放在build/目录依赖文件生成自动处理头文件依赖多种构建类型支持debug/release版本测试集成支持单元测试框架安装/卸载标准的Unix安装流程代码质量工具集成lint、内存检查等6. 运行与验证6.1 基本使用# 1. 查看帮助 $ make help # 2. 构建调试版本默认 $ make # 或 $ make BUILD_TYPEdebug # 3. 构建发布版本 $ make BUILD_TYPErelease # 4. 运行程序 $ make run # 5. 运行测试 $ make test # 6. 清理 $ make clean6.2 验证构建结果# 检查生成的文件 $ ls -la build/ total 156 drwxr-xr-x 3 user group 4096 Mar 20 10:30 . drwxr-xr-x 10 user group 4096 Mar 20 10:30 .. -rwxr-xr-x 1 user group 24576 Mar 20 10:30 myapp_debug -rw-r--r-- 1 user group 1234 Mar 20 10:30 main.o -rw-r--r-- 1 user group 1234 Mar 20 10:30 math_utils.o -rw-r--r-- 1 user group 567 Mar 20 10:30 main.d -rw-r--r-- 1 user group 567 Mar 20 10:30 math_utils.d # 检查可执行文件信息 $ file build/myapp_debug build/myapp_debug: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]..., for GNU/Linux 3.2.0, with debug_info, not stripped # 检查调试信息 $ readelf -S build/myapp_debug | grep debug [27] .debug_aranges PROGBITS 0000000000000000 00001000 [28] .debug_info PROGBITS 0000000000000000 00001040 [29] .debug_abbrev PROGBITS 0000000000000000 00001234 [30] .debug_line PROGBITS 0000000000000000 000013456.3 测试依赖文件生成修改头文件验证依赖是否生效# 1. 先构建一次 $ make clean $ make # 2. 修改头文件 $ echo // 添加新函数声明 include/project.h # 3. 再次构建应该只重新编译依赖该头文件的源文件 $ make gcc -Wall -Wextra -Wpedantic ... -c src/main.c -o build/main.o # 注意只有依赖project.h的文件被重新编译7. 常见问题与解决方案7.1 编译问题问题现象可能原因排查方式解决方案make: *** No rule to make target...1. 文件不存在2. 路径错误3. 规则未定义1. 检查文件是否存在2. 检查Makefile中的路径3. 运行make -n查看实际命令1. 确保文件存在2. 使用绝对路径或相对路径3. 添加对应的规则missing separator. Stop.命令前使用了空格而不是Tab检查Makefile中的缩进将命令前的空格改为Tabundefined reference to...1. 函数未实现2. 库未链接3. 链接顺序错误1. 检查函数声明和定义2. 检查链接库选项3. 查看链接命令1. 实现缺失的函数2. 添加-l选项链接库3. 调整链接顺序头文件找不到1. 头文件路径未指定2. 头文件不存在1. 检查-I选项2. 检查文件是否存在1. 添加-I/path/to/include2. 创建缺失的头文件7.2 依赖问题问题现象可能原因排查方式解决方案修改头文件后未重新编译1. 依赖未正确生成2..d文件未包含1. 检查.d文件内容2. 检查-include语句1. 确保依赖生成规则正确2. 使用-MMD选项自动生成循环依赖头文件互相包含检查头文件包含关系1. 使用前向声明2. 重构代码结构清理后无法构建依赖文件被清理检查clean规则在clean中保留必要的依赖文件7.3 性能问题问题现象可能原因排查方式解决方案构建速度慢1. 每次全量编译2. 依赖检查开销大1. 使用time make计时2. 检查.d文件数量1. 确保增量编译生效2. 减少不必要的依赖3. 使用ccache内存占用高1. 并行编译过多2. 单个文件太大1. 监控内存使用2. 检查文件大小1. 限制并行数-jN2. 分割大文件7.4 跨平台问题问题现象可能原因排查方式解决方案Windows下路径问题1. 反斜杠和正斜杠混用2. 盘符问题1. 检查路径分隔符2. 检查绝对路径1. 使用/作为路径分隔符2. 使用相对路径3. 使用cygpath转换工具链差异1. 命令不同2. 选项不同1. 检查uname输出2. 测试命令可用性1. 使用条件判断2. 提供替代命令库文件差异1. 库名不同2. 链接方式不同1. 检查库文件2. 测试链接1. 使用pkg-config2. 提供多个链接选项8. Makefile最佳实践8.1 代码组织建议模块化设计# 按模块组织 CORE_SRCS $(wildcard src/core/*.c) NET_SRCS $(wildcard src/net/*.c) UTIL_SRCS $(wildcard src/util/*.c) SRCS $(CORE_SRCS) $(NET_SRCS) $(UTIL_SRCS)版本信息管理# 从git获取版本信息 VERSION $(shell git describe --tags --always --dirty) GIT_COMMIT $(shell git rev-parse HEAD) BUILD_DATE $(shell date -u %Y-%m-%dT%H:%M:%SZ) CFLAGS -DVERSION\$(VERSION)\ CFLAGS -DGIT_COMMIT\$(GIT_COMMIT)\ CFLAGS -DBUILD_DATE\$(BUILD_DATE)\配置头文件生成# 生成配置头文件 CONFIG_H include/config.h $(CONFIG_H): Makefile echo Generating $ echo /* Auto-generated by Makefile */ $ echo #ifndef CONFIG_H $ echo #define CONFIG_H $ echo #define VERSION \$(VERSION)\ $ echo #define BUILD_DATE \$(BUILD_DATE)\ $ echo #endif /* CONFIG_H */ $ # 依赖配置头文件 $(OBJS): $(CONFIG_H)8.2 性能优化技巧并行构建# 使用所有CPU核心 make -j$(nproc) # 指定并行数 make -j4使用ccache加速# 检测ccache ifneq ($(shell which ccache),) CC : ccache $(CC) endif分布式构建大型项目# 使用distcc ifneq ($(shell which distcc),) CC : distcc $(CC) endif8.3 调试技巧调试Makefile执行# 显示执行的命令不执行 make -n # 显示详细调试信息 make -d # 显示变量值 make -p | grep VARIABLE打印变量值# 调试用规则 print-%: echo $* $($*) # 使用make print-CC跟踪规则执行# 添加调试输出 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c echo Compiling $ - $ $(CC) $(CFLAGS) -c $ -o $8.4 安全注意事项防止命令注入# 错误可能被注入 clean: rm -rf $(BUILD_DIR)/* # 正确固定目录 clean: rm -rf build/*生产环境保护# 确认危险操作 ifeq ($(CONFIRM),yes) deploy: scp $(TARGET) userserver:/path/ else deploy: echo Please run: make deploy CONFIRMyes exit 1 endif备份重要文件# 备份配置文件 backup-config: cp config.ini config.ini.backup.$(shell date %Y%m%d_%H%M%S)9. 高级主题与扩展9.1 静态库与动态库# 静态库 STATIC_LIB $(BUILD_DIR)/lib$(PROJECT_NAME).a $(STATIC_LIB): $(OBJS) $(AR) $(ARFLAGS) $ $^ # 动态库共享库 SHARED_LIB $(BUILD_DIR)/lib$(PROJECT_NAME).so $(SHARED_LIB): $(OBJS) $(CC) -shared -o $ $^ $(LDFLAGS) # 使用库 app_static: main.c $(STATIC_LIB) $(CC) $(CFLAGS) -o $ $ -L$(BUILD_DIR) -l$(PROJECT_NAME) app_shared: main.c $(SHARED_LIB) $(CC) $(CFLAGS) -o $ $ -L$(BUILD_DIR) -l$(PROJECT_NAME) -Wl,-rpath,$(BUILD_DIR)9.2 条件编译与特性开关# 特性开关 FEATURE_A ? 0 FEATURE_B ? 1 ifeq ($(FEATURE_A),1) CFLAGS -DFEATURE_A endif ifeq ($(FEATURE_B),1) CFLAGS -DFEATURE_B endif # 在代码中使用 #ifdef FEATURE_A // 特性A的代码 #endif9.3 集成测试框架# 使用Check框架 CHECK_CFLAGS $(shell pkg-config --cflags check) CHECK_LDFLAGS $(shell pkg-config --libs check) # 测试目标 TEST_TARGET $(BUILD_DIR)/test_runner TEST_SRCS $(wildcard tests/*.c) TEST_OBJS $(TEST_SRCS:.c.o) $(TEST_TARGET): $(OBJS) $(TEST_OBJS) $(CC) -o $ $^ $(LDFLAGS) $(CHECK_LDFLAGS) test: $(TEST_TARGET) ./$(TEST_TARGET) # 覆盖率报告 coverage: CFLAGS --coverage coverage: LDFLAGS --coverage coverage: clean test lcov --capture --directory . --output-file coverage.info genhtml coverage.info --output-directory coverage_report9.4 交叉编译支持# 交叉编译工具链 CROSS_COMPILE ? arm-linux-gnueabihf- CC $(CROSS_COMPILE)gcc AR $(CROSS_COMPILE)ar STRIP $(CROSS_COMPILE)strip # 目标架构特定选项 ifeq ($(CROSS_COMPILE),arm-linux-gnueabihf-) CFLAGS -marcharmv7-a -mfpuneon -mfloat-abihard endif # 安装到目标路径 TARGET_PREFIX ? /opt/arm-rootfs install-cross: $(TARGET) install -d $(TARGET_PREFIX)/usr/bin $(STRIP) $(TARGET) install -m 755 $(TARGET) $(TARGET_PREFIX)/usr/bin/9.5 与CMake的对比特性MakefileCMake学习曲线陡峭但核心概念简单平缓但抽象层次多跨平台需要手动处理差异自动处理平台差异依赖管理需要手动编写规则自动查找依赖生成器支持只生成Makefile可生成多种构建系统文件大型项目维护困难更适合大型项目灵活性完全控制灵活约定优于配置选择建议小型到中型C项目纯Makefile足够跨平台C/C项目考虑CMake需要集成多种编译器CMake更合适教育/学习目的从Makefile开始10. 实战练习重构你的项目现在用你学到的知识重构一个现有项目10.1 步骤指南分析现有项目结构# 查看项目文件 find . -name *.c -o -name *.h | sort # 查看编译命令 grep -r gcc\|clang ./创建基础Makefile# 从最简单的开始逐步添加功能 CC gcc CFLAGS -Wall -Wextra TARGET myapp SRCS $(wildcard src/*.c) OBJS $(SRCS:.c.o) all: $(TARGET) $(TARGET): $(OBJS) $(CC) -o $ $^ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(OBJS) $(TARGET) .PHONY: all clean逐步添加功能添加头文件依赖生成添加调试/发布模式添加测试目标添加安装规则添加代码质量检查测试每个功能# 测试基本编译 make clean make # 测试增量编译 touch src/main.c make # 测试头文件依赖 touch include/header.h make # 测试不同构建类型 make BUILD_TYPErelease10.2 常见重构问题文件太多怎么办# 使用通配符 SRCS $(wildcard src/*.c) $(wildcard src/*/*.c)依赖关系复杂怎么办# 让编译器生成依赖 CFLAGS -MMD -MP -include $(OBJS:.o.d)需要链接多个库怎么办# 使用pkg-config CFLAGS $(shell pkg-config --cflags libxml-2.0) LDFLAGS $(shell pkg-config --libs libxml-2.0)10.3 验证你的Makefile创建一个测试项目验证功能# 创建测试目录 mkdir -p test_project/{src,include,tests} cd test_project # 创建示例文件 cat src/main.c EOF #include stdio.h #include math.h int main() { printf(2 3 %d\n, add(2, 3)); printf(2 - 3 %d\n, sub(2, 3)); return 0; } EOF cat src/math.c EOF #include math.h int add(int a, int b) { return a b; } int sub(int a, int b) { return a - b; } EOF cat include/math.h EOF #ifndef MATH_H #define MATH_H int add(int a, int b); int sub(int a, int b); #endif EOF # 使用你的Makefile测试 cp /path/to/your/Makefile . make make run make test # 如果有测试 make clean掌握Makefile不是一蹴而就的最好的学习方式是在实际项目中应用。从今天开始为你下一个C项目写一个专业的Makefile你会发现代码管理变得如此简单高效。
返回列表