Makefile与CMake
一、Makefile 核心内容
1. Makefile 基础结构与工作原理
- 三要素:
- 目标(Target):要生成的文件或执行的操作(如可执行文件、清理操作)。
- 依赖(Dependency):生成目标所需的文件或其他目标。
- 命令(Command):生成目标的具体指令(需以Tab 键开头)。
- 工作原理:
Make 通过检查依赖文件的修改时间,仅重新编译更新过的文件,提高编译效率。例如:simple: main.o foo.o # 目标:simple,依赖:main.o和foo.ogcc -o simple main.o foo.o # 命令 main.o: main.c # 子目标:main.o依赖main.cgcc -c main.c -o main.o
2. 关键特性与语法
-
伪对象(.PHONY):
避免 Make 将目标视为同名文件,强制执行命令。例如:.PHONY: clean clean:rm simple main.o foo.o
- 若不声明
.PHONY
,当目录存在clean
文件时,make clean
会认为目标已更新,不执行删除命令。
- 若不声明
-
变量与自动变量:
- 自定义变量:用于存储重复内容(如编译器、文件列表),例:
CC = gcc SRCS = main.c foo.c OBJS = $(SRCS:.c=.o) # 将.c替换为.o
- 自动变量:
$@
:当前目标名(如simple
)。$^
:所有依赖文件(如main.o foo.o
)。$<
:第一个依赖文件(如main.c
)。
$(EXE): $(OBJS)$(CC) -o $@ $^ # 等价于gcc -o simple main.o foo.o
- 自定义变量:用于存储重复内容(如编译器、文件列表),例:
-
函数与第三方库依赖:
wildcard
:获取指定模式的文件列表,例:SRCS = $(wildcard *.c)
。patsubst
:字符串替换,例:OBJS = $(patsubst %.c, %.o, $(SRCS))
。- 第三方库链接:通过
-I
指定头文件路径,-L
指定库路径,-l
指定库名,例:makefile
CFLAGS += -I./include LDFLAGS += -L./lib -lpthread
3. 实战范例
-
简单示例:
all: test@echo "hello all" test:@echo "hello test"
make
默认执行第一个目标(all
),依赖test
,因此先执行test
再执行all
。
-
复杂编译流程:
通过变量和自动变量简化多文件编译,例:CC = gcc SRCS = main.c foo.c OBJS = $(SRCS:.c=.o) EXE = simple$(EXE): $(OBJS)$(CC) -o $@ $^%.o: %.c$(CC) -c $< -o $@
二、CMake 核心内容
1. CMake 概述
- 定位:跨平台构建工具,通过编写
CMakeLists.txt
生成 Makefile 或其他项目文件(如 VS 工程),简化多平台编译配置。 - 优势:相比 Makefile,语法更简洁,支持模块化设计,适合大型项目。
2. 基础语法与流程
- 核心命令:
cmake_minimum_required
:指定 CMake 最低版本。project
:定义项目名称和语言(如C
、CXX
)。add_executable
:添加可执行文件,关联源文件。add_library
:生成库文件(SHARED
动态库,STATIC
静态库)。target_link_libraries
:链接库文件到可执行文件。
- 编译流程:
- 在项目根目录创建
CMakeLists.txt
。 - 创建
build
目录,进入后执行cmake ..
生成 Makefile。 - 执行
make
编译。
- 在项目根目录创建
3. 实战场景
-
单文件编译:
cmake_minimum_required(VERSION 2.8) project(0voice) set(SRC_LIST main.c) add_executable(0voice ${SRC_LIST})
-
多目录与库管理:
- 子目录编译为库:
# 根目录CMakeLists.txt add_subdirectory(src/dir1) # 添加子目录 add_subdirectory(src/dir2) add_executable(main main.c) target_link_libraries(main dir1 dir2) # 链接库
# src/dir1/CMakeLists.txt add_library(dir1 SHARED dir1.c) # 生成动态库
- 强制使用静态库:
target_link_libraries(main libdir1.a) # 指定静态库文件名
- 子目录编译为库:
-
安装与编译选项:
- 指定安装路径:
cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. # 安装到/usr/local make install # 安装库、头文件等到目标路径
- Debug/Release 模式:
if(${CMAKE_BUILD_TYPE} MATCHES "Release")set(CMAKE_CXX_FLAGS "-O3 -Wall") # Release优化 else()set(CMAKE_CXX_FLAGS "-O0 -g") # Debug调试符号 endif()
- 指定安装路径:
特性 | Makefile | CMake |
---|---|---|
学习难度 | 较高(语法灵活但复杂) | 较低(模块化命令,易上手) |
跨平台支持 | 依赖平台特定语法(如 GNU Make) | 原生支持多平台(生成对应平台构建文件) |
大型项目管理 | 手动维护依赖关系,易出错 | 支持子目录、库管理,自动处理依赖 |
适用场景 | 简单项目或需要精细控制编译流程的场景 | 复杂多模块项目、跨平台开发 |
建议:
- 小型项目或需要深入理解编译原理时,使用 Makefile。
- 中大型项目或跨平台开发时,优先选择 CMake,搭配
make
执行编译。
三、Makefile和CMake的具体案例
案例 1:Makefile 跨目录编译
项目结构
project_make/ ├── include/ # 头文件目录 │ └── utils.h ├── src/ # 源文件目录 │ ├── main.c │ └── utils/ │ └── util.c ├── build/ # 输出目录(存放目标文件和可执行文件) └── Makefile
代码文件内容
include/utils.h
(头文件)#ifndef UTILS_H #define UTILS_Hint add(int a, int b);#endif
src/utils/util.c
(功能实现)#include "utils.h"int add(int a, int b) {return a + b; }
src/main.c
(主函数)#include <stdio.h> #include "utils.h"int main() {int sum = add(3, 5);printf("3 + 5 = %d\n", sum);return 0; }
Makefile 实现
# 变量定义 CC = gcc CFLAGS = -Wall -I./include # -I 指定头文件路径 SRCS = $(wildcard src/*.c src/utils/*.c) # 匹配所有源文件 OBJS = $(patsubst %.c, build/%.o, $(SRCS)) # 目标文件路径(build目录下保持原目录结构) EXE = build/app # 最终可执行文件路径# 生成可执行文件(默认目标) all: $(EXE)# 链接目标文件生成可执行文件 $(EXE): $(OBJS)@mkdir -p $(dir $@) # 创建输出目录(若不存在)$(CC) $^ -o $@# 模式规则:编译 .c 文件为 .o(保持目录结构) build/%.o: %.c@mkdir -p $(dir $@) # 创建目标文件所在目录(如 build/src/utils/)$(CC) $(CFLAGS) -c $< -o $@# 清理生成文件 .PHONY: clean clean:rm -rf build/
操作说明
- 执行
make
,会自动:
- 在
build
目录下生成src/main.o
、src/utils/util.o
目标文件。- 链接生成可执行文件
build/app
。- 运行
./build/app
,输出3 + 5 = 8
。案例 2:CMake 跨目录编译
项目结构
project_cmake/ ├── include/ # 头文件目录 │ └── utils.h ├── src/ # 源文件目录 │ ├── main.c │ └── utils/ │ ├── util.c │ └── CMakeLists.txt # 子目录 CMake 配置 ├── build/ # 编译目录(手动创建) └── CMakeLists.txt # 根目录 CMake 配置
代码文件内容
头文件
include/utils.h
、src/utils/util.c
、src/main.c
与 Makefile 案例完全相同。CMake 配置文件
- 根目录
CMakeLists.txt
:cmake_minimum_required(VERSION 3.10) project(MyProject)# 指定 C 标准(可选) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON)# 包含头文件目录(全局生效) include_directories(include)# 添加子目录(会执行 src/utils/CMakeLists.txt) add_subdirectory(src/utils)# 定义主程序源文件(仅主函数) set(MAIN_SRC src/main.c)# 生成可执行文件(链接子目录生成的库) add_executable(app ${MAIN_SRC}) target_link_libraries(app utils) # 链接子目录生成的库
- 子目录
src/utils/CMakeLists.txt
:# 定义当前目录的源文件(仅功能实现) set(UTIL_SRC util.c)# 生成静态库(库名:utils) add_library(utils STATIC ${UTIL_SRC})# 可选:设置库的输出目录(例如放到 build/lib) set_target_properties(utils PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
操作说明
- 进入
build
目录(需手动创建):mkdir build && cd build
- 执行
cmake ..
生成构建文件(会自动处理跨目录依赖)。- 执行
make
编译,生成:
- 静态库
build/lib/libutils.a
(子目录生成)。- 可执行文件
build/app
(根目录生成)。- 运行
./app
,输出3 + 5 = 8
。
0voice · GitHub