一般来说,无论是C或者C++工程,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File、resource和依赖的库文件合成执行文件,这个动作叫作链接(link)。
- 编译: 编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。
- 链接: 主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a (静态库)/.so(动态库)文件。
- Makefile定义了C/C++工程项目编译管理的规则
一个简单的示例
先定义三个文件: hellomake.c, hellofunc.c, and hellomake.h
hellomake.c | hellofunc.c | hellomake.h |
---|---|---|
#include <hellomake.h> int main() { return(0); | #include <stdio.h> void myPrintHelloMake(void) { printf("Hello makefiles!\n"); return; | /* void myPrintHelloMake(void); |
通过如下命令来编译上面的三个文件:
gcc -o hellomake hellomake.c hellofunc.c -I.
上面的命令会编译hellomake.c和hellofunc.c 2个文件,然后链接生成一个hellomake的可执行文件。
"-I." 代表gcc 在当前目录下开始搜索头文件。如果不使用makefile, 每次修改、更新或新增文件,我们要反复执行上面的命令进行重新编译和链接。
Makefile循序渐进
最简单的一个Makefile 就是把编译命令保存一下,每次直接执行make命令就可以完成编译和链接
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
Makefile版本1 指明了一条编译规则,":"左边指明了规则的目标对象,右边指明了依赖的相关源文件,如果右边的相关文件有改动,这个规则在执行make时就会被触发执行。第二行开始空一个tab空格,gcc执行编译和链接命令。
版本1是只包含一条规则的编译指令,而一般的项目工程会有多条编译命令,开发迭代的过程中需要修改相关的编译工具或头文件路径,我们就要会去修改每条具体的规则,为了更高效一点,我们可以把公用的工具和路径以macro的形式先声明后使用。具体d的优化方案参考版本2:
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o $(CFLAGS)
版本2定义几个macro:CC和CFLAGS。它告诉我们如何在构建和编译的过程中使用这些宏。CC是c编译器要使用的命令,CFLAGS指明了编译要搜索的头文件路径。版本2已经可以解决很多小型项目的编译的流程,但是还有个问题就是如果我们修改了.h文件,而Makefile又没有定义相关的依赖规则,对应的.c文件没有变动所以不会重新编译,这是个很常见的问题。
为了修复这个问题,我们需要为.h 和.c增加额外的依赖规则。当.h 和 .c 文件变化时,我们执行make要触发编译。版本3新增了一条规则去修复这个问题。
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
版本3新增了一个macro DEPS, 这个宏定义了所有头文件的集合,这样做的好处是:某些头文件可能被很多.c文件包含,如果不提取出来,新增一个.h 就必须得修改相应的依赖规则,这是很不友好的。正如本文开始所描述的编译过程,编译会先生成一个.o文件,我们可以针对.o定义一个依赖规则,"%" 通配符代表所有中间编译生成的目标对象,这些对象依赖.c 文件和相应的一个或多个头文件,一旦这些文件中的某些文件发生变化时,这条编译规则会被触发。"-c" 代表我们要生成.o目标文件, "-o $@" 代表 编译规则中":"左边的所有对象, "$<" 代表使用依赖列表的第一项规则。
作为一个通用标准,hellomake依赖的.o也可能在其他的目标链接文件中引入,所以这条规则我们可以提取出来,使用macro通配,如版本4: $@ 和 $^ 代表最终链接hellomake规则的":"左右两边的内容:
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
一般的工程项目会把头文件.h、源文件.c、库文件以及source分开放在不同的目录里面,同时编译生成的中间.o的结果文件希望隔离保存,链接完成之后在bin文件保存可执行目标文件, 其它.o及中间结果可以删除。我们需要增加额外的macro,只要执行make clean 就可以删除相应的中间文件,此处为了避免和可能存在的clean目标或文件冲突,我们引入伪目标.PHONY, 它可以保存以clean为目标的文件,避免覆盖。
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
经过不断的改造,版本5开起来是个非常规范的Makefile文件,它定义模板规则已经依赖的编译规则,抽取公用的macro来适配所以的规则,这样可以更方便的增加规则到makefile文件里面去。