功能
一个工程中源代码不计其数,其按类型、功能、模块分别放在若干个目录中。
如src:.c/.cpp源代码
include:.h头文件
lib:libxx.so/libxx.a库
bin:生成的二进制文件
obj:生成的.o文件,用于下一步的动态链接
makefile制定了一系列规则来指定 哪些文件需要编译,怎么编译的,先后顺序是怎么样的。
makefile就像shell脚本一样,可以执行操作系统的命令,只需要make,make clean指令即可实现自动化编译和自动化删除构建的文件,极大提高了开发效率。
一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
Cmake(Cmakelist.txt)也是一种自动化编译的工具,其编写更复杂,底层还是通过自动生成makefile文件再执行make命令的。
configure脚本也是一种自动化编译的工具,执行./configure后根据脚本生成makefile,再执行make命令。
make规则
target: prerequisites
command
#target是生成的文件
#prerequisites是需要的文件
#command是这个过程中需要执行的命令
其实就是根据makefile文件定义的编译流程,不断倒推执行,知道编译出最终的target,如下是一个简单的例子
main:file1.o file2.o
gcc -o main file1.o file2.o
file1.o:file1.c
gcc file1.c -o file1.o
file2.o:file2.c
gcc file2.c -o file2.o
如果编译需要指定编译选项,文件不在同一目录,编译过程需要指定头文件路径,编译后的结果也应该跟源文件分开放,编译器需要能方便同时管理,接下来看下面的例子
假设项目目录是
project/
├── Makefile
├── src/
│ ├── main.c
│ ├── foo.c
│ ├── foo.h
│ ├── bar.c
│ └── bar.h
├── include/
│ └── common.h
├── lib/
│ └── libmylib.a
├── test/
│ ├── test_foo.c
│ └── test_bar.c
└── build/
# 指定编译器和链接器
CC = gcc
CXX = g++
AR = ar
# 编译选项
CFLAGS = -Wall -Wextra -Iinclude -g
LDFLAGS = -Llib -lmylib
# 目标可执行文件名称
TARGET = build/myprogram
# 源文件和对象文件
SRCS = src/main.c src/foo.c src/bar.c
OBJS = $(SRCS:%.c=build/%.o)
# 单元测试
TEST_SRCS = test/test_foo.c test/test_bar.c
TEST_OBJS = $(TEST_SRCS:%.c=build/%.o)
TEST_TARGET = build/tests
# 默认目标
all: $(TARGET)
# 链接目标可执行文件
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
# 编译对象文件
build/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c -o $@ $<
# 单元测试目标
tests: $(TEST_TARGET)
$(TEST_TARGET)
# 链接单元测试可执行文件
$(TEST_TARGET): $(OBJS) $(TEST_OBJS)
$(CC) $(CFLAGS) -o $@ $^
# 静态库生成
lib/libmylib.a: $(OBJS)
$(AR) rcs $@ $^
# 清理
clean:
rm -rf build
# 伪目标
.PHONY: all clean tests
想要看懂以上代码需要以下知识:
- 使用“=”,进行变量定义,类似于宏,当想改变全局的某一变量,如编译器,只需要改变定义
- OBJS = $(SRCS:%.c=build/%.o)表示源文件对应的对象文件,这里指build/main.o build/foo.o build/bar.o
- 为了方便指代target和prerequisites,使用$@、$^、$<分别代表target、所有依赖文件列表、第一个依赖文件名
以上是本人学习过程中的认识,如有错误,请指正批评