一起玩转Makefile

一般来说,无论是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.chellofunc.chellomake.h

#include <hellomake.h>

int main() {
  // call a function in another file
  myPrintHelloMake();

  return(0);
}
 

#include <stdio.h>
#include <hellomake.h>

void myPrintHelloMake(void) {

  printf("Hello makefiles!\n");

  return;
}
 

/*
example include file
*/

void myPrintHelloMake(void);
 

通过如下命令来编译上面的三个文件:

gcc -o hellomake hellomake.c hellofunc.c -I.

上面的命令会编译hellomake.c和hellofunc.c 2个文件,然后链接生成一个hellomake的可执行文件。

"-I." 代表gcc 在当前目录下开始搜索头文件。如果不使用makefile, 每次修改、更新或新增文件,我们要反复执行上面的命令进行重新编译和链接。

Makefile循序渐进

最简单的一个Makefile 就是把编译命令保存一下,每次直接执行make命令就可以完成编译和链接

Makefile 1

hellomake: hellomake.c hellofunc.c
     gcc -o hellomake hellomake.c hellofunc.c -I.

Makefile版本1 指明了一条编译规则,":"左边指明了规则的目标对象,右边指明了依赖的相关源文件,如果右边的相关文件有改动,这个规则在执行make时就会被触发执行。第二行开始空一个tab空格,gcc执行编译和链接命令。

版本1是只包含一条规则的编译指令,而一般的项目工程会有多条编译命令,开发迭代的过程中需要修改相关的编译工具或头文件路径,我们就要会去修改每条具体的规则,为了更高效一点,我们可以把公用的工具和路径以macro的形式先声明后使用。具体d的优化方案参考版本2:

Makefile 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新增了一条规则去修复这个问题。

Makefile 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规则的":"左右两边的内容:

Makefile 4

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为目标的文件,避免覆盖。

Makefile 5

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文件里面去。

参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值