makefile高级技巧

在Linux下构建一个C/C++工程一般都会使用Make工具,这就避免不了写makefile。相对于Windows平台上visual studio的人性化构建,手动写makefile确实是一件头痛的事,特别是构建一个大型的工程。一些人可能觉得makefile不但难写,构建功能还不如vs强大,当然前者我不否认,但是若能灵活运用的话,makefile的自动化构建能力绝对超过vs。这里不打算讲makefile的基本使用(初学者可以参考官方文档和网上的makefile写法示例),主要分享下makefile的一些高级技巧和常见构建问题的解决方案。

一、头文件依赖问题

一个大型c++项目会包含大量源文件,如果每次构建都编译所有cpp文件的话会让你等的痛不欲生。因此我们可以只对修改过的cpp文件进行编译,当然makefile也提供了这个基本功能,这是通过文件的修改时间来判定,比如”user.o:  user.cpp”表示若user.cpp的修改时间比user.o更新的话才执行编译命令。但这里有个问题,若user.cpp中include了”foo.h”头文件,这个头文件有修改而user.cpp没修改makefile也不会重新编译user.cpp,这不是我们所希望的。一个简单的解决方法是把user.cpp包含的所有头文件名都写成user.o的依赖,这样头文件的修改也会引起user.o的重新生成,但是如果foo.h又包含了其他头文件呢,难道要把user.cpp直接和间接包含的所有文件名都手动写到makefile里吗?如果包含的头文件列表有改动也要人肉改写makefile吗?你不觉得这样会吐血吗?我们很自然的会想有没有自动维护头文件依赖的方法,遗憾的是makefile没有提供这个功能,不过编译器却提供了。gcc\g++有个预处理选项”-MM”,可以把源文件的所有直接或间接包含的非系统路径(系统路径头文件指用尖括号包含的头文件,如<vector>,一般认为不会被修改) 的头文件以makefile可读的格式输出出来,我们可以把这个依赖关系保存成一个依赖文件user.d,在makefile中include这个文件。另外,user.d这个依赖文件的重新生成也应依赖于user.cpp和其包含的头文件的修改,也就是和user.o的依赖关系一致,所以我们要在user.o之后、冒号之前加上”user.d”,最终的user.d内容如下:

user.o user.d:  user.cpp  foo.h ….

官方文档提供的makefile写法如下:

%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed ‘s,\($*\)\.o[ :]*,\1.o $@ : ,g’ < $@.$$$$ > $@; \
rm -f $@.$$$$

include $(sources:.c=.d)

不解释。

二、makefile的递归构建
一个简单的软件写一个makefile就可以了,所有源文件的编译和最终链接全搞定。大型的项目则由许多的库组成,我们把许多一个模块的许多源文件编译成一个库文件(静态链接或动态链接均可),最终把这些库文件链接成可执行文件。这和vs的解决方案与项目类似。我们可以在每个库目录下写一个makefile,负责构建这个库文件。另外还需一个最终链接的总makefile,他会调用所有配置好的库目录下的makefile,然后按需进行链接,链接命令依赖的是那些库文件名,如果所有的库文件都没有更新的就不需重新链接了。
示例如下:

SUBDIRS = foo bar baz  #所有库目录路径
LIBS = foo.a bar.a baz.a #库文件名

.PHONY: all
all: subdirs      #先编译所有库文件
$(MAKE) $(PROG)  #执行链接任务

$(PROG): $(LIBS)  #若库文件更新才进行链接
链接命令

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):
$(MAKE) -C $@   #调用库目录下的makefile

三、debug和release版本分离
vs的项目构建可以选择debug和release版本,两者互不影响,在makefile中怎么实现呢?通常做法是编译不同版本的object文件,它们可以放在不同的目录下,如vs就会分别放在debug和release目录下。
想偷懒的话也可以放在一个目录,但object文件的后缀不能相同,如release版本的用”.o”,debug版本的用”.do”,这样也需要不同版本的依赖文件(因为依赖文件中写明了object文件名),可以分别用”.d”和”.dd”作后缀。
示例如下:
ifeq ($(DEBUG),1)  #debug版本
CXXFLAGS_EXTRA=-pg -g  #开启调试和profile编译选项
DEP_TAIL=.dd
OBJ_TAIL=.do
PROG := /usr/local/nmt/bin/nmt_server_debug
else  #release版本
CXXFLAGS_EXTRA=-O3  #开启最高优化编译选项
DEP_TAIL=.d
OBJ_TAIL=.o
PROG := /usr/local/nmt/bin/nmt_server
endif

之后把这些变量放在合适的地方,你懂的。

四、并行构建
默认情况下,make是单任务构建的,但现今的cpu都是多核的,如果可以同时编译不同的文件可以大大减少构建时间,幸运的是make直接支持了并行构建的功能(vs没有提供,鄙视)。在make命令后直接加上”-j”选项就可以了,make会自动开启多个进程来处理任务,并自动管理这些进程。比如链接任务是”$(PROG): foo.o bar.o baz.o”,make会同时开启3个进程同时编译foo.o bar.o baz.o,等这些进程都成功结束后再执行后面的链接命令。直接加”-j”选项不限定最大的并行任务数量,也可以再后面加上数字来限制它,如”make -j4″表示最多开启4个并行任务,这个数量写cpu的核数或其两倍比较好。个人经验:限定最大并行数比不限定时,cpu的利用率更高。

转载于:https://my.oschina.net/mavericsoung/blog/118455

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值