1、利用makefile 来构建和管理自己的工程。
2、-I、-l、-L 后面的参数均没有空格
Makefile 中:= ?= += =的区别
在Makefile中我们经常看到 = := ?= +=这几个赋值运算符,那么他们有什么区别呢?我们来做个简单的实验
新建一个Makefile,内容为:
ifdef DEFINE_VRE
VRE = “Hello World!”
else
endif
ifeq ($(OPT),define)
VRE ?= “Hello World! First!”
endif
ifeq ($(OPT),add)
VRE += “Kelly!”
endif
ifeq ($(OPT),recover)
VRE := “Hello World! Again!”
endif
all:
@echo $(VRE)
敲入以下make命令:
make DEFINE_VRE=true OPT=define 输出:Hello World!
make DEFINE_VRE=true OPT=add 输出:Hello World! Kelly!
make DEFINE_VRE=true OPT=recover 输出:Hello World! Again!
make DEFINE_VRE= OPT=define 输出:Hello World! First!
make DEFINE_VRE= OPT=add 输出:Kelly!
make DEFINE_VRE= OPT=recover 输出:Hello World! Again!
从上面的结果中我们可以清楚的看到他们的区别了
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
之前一直纠结makefile中“=”和“:=”的区别到底有什么区别,因为给变量赋值时,两个符号都在使用。网上搜了一下,有人给出了解答,但是本人愚钝,看不懂什么意思。几寻无果之下,也就放下了。今天看一篇博客,无意中发现作者对于这个问题做了很好的解答。解决问题之余不免感叹,有时候给个例子不就清楚了吗?为什么非要说得那么学术呢。^_^
1、“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
x = foo
y = $(x) bar
x = xyz
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
2、“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo
y := $(x) bar
x := xyz
在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。
2、先看下面的Makefile:
- #example
- B := $(A)
- A = later
- all:
- @echo $(B)
- #example
- B = $(A)
- A = later
- all:
- @echo $(B)
拿clean举例,如果make完成后,自己另外定义一个名叫clean的文件,再执行make clean时,将不会执行rm命令。
为了避免出现这个问题,需要.PHONY: clean
=======================================================================================
所谓伪目标就是这样一个目标,它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,有时我们将一个伪目标成为标签。
那么到底什么是伪目标呢?可能作为初学者还不会在乎这个问题,下面我们来看下我们将在什么时候需要它。
首先来看下面一个例子:
当前目录下只有一个myls1.c,于是为了让程序让makefile来管理,写了一个如下的简单的makefile。
执行:
大家会发现,真的可以利用这个makefile管理当前的工程,也能如期按照我们的要求生成执行文件myls。
执行make clean,这样就可以删除可执行程序。
接着我做了个手脚,在当前目录下建立一个叫clean的文件,那么这样执行的效果是如何?
那么这个时候为什么又不能执行了?在我的makefile中其实并没有修改任何东西,为什么这个时候已经能管理工程的makefile又不能来管理文件了。
那要解决这个问题就是添加两行,修改后的makefile如下:
再次返回执行:
这样就解决了问题,那具体的原因是什么?
在makefile中我们使用伪目标就可以解决上述的问题,那为什么要使用伪目标,一种就是如例题,为了避免在makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,另一种是提交执行makefile时的效率。
第一种情况:
如果我们需要书写这样的一个规则:规则所定义的命令不是去创建目标文件,而是通过make命令行明确指定它来执行一些特点的命令,就像例题中的clean。当文件夹中没有clean这个文件的时候,我们输入“make clean”能按照初衷执行,但是一旦文件夹中出现clean文件,我们再次输入“make clean”,由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执行规则所定义的命令。所以rm命令不会被执行。为了解决问题,我们将目标clean定义成伪目标。
也就是添加:
.PHONY:clean
那么目录中不论是否有clean文件,只要输入“make clean”就能执行rm命令了。
当一个目标被声明为伪目标后,make在执行规则时不会去试图去查找隐含规则来创建它。这样就提高了make的执行效率,也不用担心由于目标和文件名重名了。
4、Makefile: $^ $^ $@ 与 ..c.o
2010-05-16 15:19
gcc -c main.c
gcc -c mytool1.c
gcc -c mytool2.c
gcc -o main main.o mytool1.o mytool2.o
Makefile文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
Makefile中也#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说
明.一般的格式是:
target: components
TAB rule
Makefile有三个非常有用的变量.分别是$@,$^,$<代表的意义分别是:
$@--目标文件,$^--所有的依赖文件
,$<--第一个依赖文件.
如果我们使用上面三个变量,那么我们可以简化我们的
Makefile文件为:
# 这是简化后的
Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<</span>
mytool1.o:mytool1.c mytool1.h
gcc -c $<</span>
mytool2.o:mytool2.c mytool2.h
gcc -c $<</span>
经过简化后我们的Makefile是简单了一点,不过人们有时候还想简单一点.这里我们学习一个Makefile的缺省规则
..c.o:
gcc -c $<</span>
这个规则表示所有的 .o文件都是依赖与相应的.c文件的.例如
mytool.o依赖于mytool.c
这样 Makefile还可以变为:
Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
..c.o:
gcc -c $<</span>
一个简单的通用Makefile实现
Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,即每次重新make时只需要处理那些修改过的文件即可。Makefile拥有很多复杂的功能,这里不可能也没必要一一介绍,为了简化问题的复杂性,本文仅和大家讨论针对单目录下的C/C++项目开发,如何写一个通用的Makefile。
首先,我们假设当前工程目录为prj/,该目录下有6个文件,分别是:main.c、abc.c、xyz.c、abc.h、xyz.h和Makefile。其中main.c包含头文件abc.h和xyz.h,abc.c包含头文件abc.h,xyz.c包含头文件xyz.h,而abc.h又包含了xyz.h。它们的依赖关系如图1。
图1 文件依赖关系
第一次使用Makefile应该写成这个样子(假设生成目标main):
main:main.o abc.o xyz.o
gcc main.o abc.o xyz.o -o main
main.o:main.c abc.h xyz.h
gcc -c main.c –o main.o -g
abc.o:abc.c abc.h xyz.h
gcc -c abc.c –o abc.o -g
xyz.o:xyz.c xyz.h
gcc -c xyz.c -o xyz.o -g
clean:
rm main main.o abc.o xyz.o -f
虽然这样Makefile完全符合Makefile的书写规则,但是当代码文件再增加几倍后,再管理这些命令将会是一个噩梦!!!因此Makefile提供了默认规则和自动推导帮我们完成一些常用功能。然后,我们将Makefile修改如下:
EXE=main
CC=gcc
OBJ=main.o abc.o xyz.o
CFLAGS=-g
(EXE):(OBJ)
(CC)^ -o $@
clean:
rm (EXE)(OBJ) -f
变量EXE,CC,OBJ分别代指目标程序名,编译器名,目标文件名。CFLAGS是Makefile的预定义变量,它会附加在每条编译命令(gcc -c)之后。
$(EXE)是对变量的引用,$^代指所有的依赖项——即$(OBJ),$@代指目标项——即$(EXE)。该命令等价于:(CC)(OBJ) -o $(EXE)。
这个Makefile只有目标文件链接的命令,源文件的编译命令都被忽略了!这正是Makefile的自动推导功能——它可以将目标文件自动依赖于同名的源文件,即:
main.o:main.c
gcc -c main.c -o main.o
abc.o:abc.c
gcc -c abc.c -o abc.o
xyz.o:xyz.c
gcc -c xyz.c -o xyz.o
按照上述方式,只要工程下增加了源文件后,只需要在OBJ初始化处增加一个*.o即可。但是这种方式是有问题的,Makefile的自动推导功能只会推导出目标文件对源文件的依赖关系,而不会增加头文件的依赖关系!!!这导致的直接问题就是修改项目的头文件,不会导致make的自动更新!除非修改头文件后运行一次make clean,再运行make…… :-)
为了能让make自动包含头文件的依赖关系,我们需要做一点额外的工作。幸运的是gcc为我们提供了一个编译选项(gcc -M,对于g++是-MM),能输出目标文件的依赖关系!比如:
$gcc -M main.c
main.o:main.c abc.h xyz.h
如果将每个源文件的依赖关系包含到Makefile里,就可以使得目标文件自动依赖于头文件了!再次修改原先的Makefile:
EXE=main
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CFLAGS=-g
all:depend $(EXE)
depend:
@(CC)−MM(SRC) > .depend
-include .depend
(EXE):(OBJ)
(CC)(OBJ) -o $(EXE)
clean:
@rm (EXE)(OBJ) .depend -f
我们虚设了一个目标all,它依赖于depend和实际的目标EXE。而depend正式将所有的源文件对应的目标文件的依赖关系输入到.depend文件,并包含在Makefile内!这里有几个细节需要说明:
1..depend文件是隐藏文件,避免和工程的文件混淆。
2.include命令之前增加符号‘-’,避免第一次make时由于.depend文件不存在报告错误信息。
3.SRC初始化为wildcard *.c表示当前目录下的所有.c源文件,这就省去了我们手动输入新增的源文件。
4.OBJ初始化为SRC:.c=.o,表示将SRC中所有.c结尾的文件名替换为.o结尾的,这样就自动生成了源文件的目标文件序列。
5.clean的rm命令钱@符号表示执行该命令时不输出任何信息。
这样,每次执行make时都会重新计算目标文件的依赖关系,并输出到.depend文件,然后包含到Makefile后进行编译工作,这样目标文件的依赖关系就不会出错了!而我们得到了一个能自动包含源文件和识别头文件依赖关系的Makefile,将该文件应用于任何单目录的C/C++工程(C++需要修改部分细节,不作赘述)都能正常工作。
但是,这种方式也有一定的不足,当头文件的依赖关系不发生变化时,每次make也会重新生成.depend文件。如果这样使得工程的编译变得不尽人意,那么我们可以尝试将依赖文件拆分,使得每个源文件独立拥有一个依赖文件,这样每次make时变化的只是一小部分文件的依赖关系。
EXE=main
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
DEP=(patsubst(SRC))
CFLAGS=-g
(EXE):(OBJ)
(CC)^ -o $@
$(DEP):.%.d:%.c
@set -e;\
rm -f $@;\
(CC)−M< > @.$$$;\
sed 's,$∗\.o[ :]*,\1.o @:,g′<</span>@.
rm -f @.$$$
-include $(DEP)
clean:
@rm (EXE)(OBJ) $(DEP) -f
该Makefile增加了一个变量DEP,初始化为patsubst %.c,.%.d,$(SRC),表示将SRC中的以*.c结尾的源文件名替换为.*.d的形式,比如main.c对应着文件.main.d,这就是main.c的依赖关系文件,且是隐藏的。
为了生成每个源文件的依赖文件,建立了目标依赖关系$(DEP):.%.d:%.c,该关系表示,对于目标DEP,通过$@可以访问一个依赖文件,通过$>则访问对应的同名源文件。命令部分使用\连接,表示当前命令作为一个整体在一个进程内执行。该组命令的含义是:将gcc -M生成的信息输出到一个临时文件,然后在:之前加上当前的文件名输出到依赖文件。比如对于main.c生成的临时文件信息为:
main.o:main.c abc.h xyz.h
处理后依赖文件信息是:
main.o .main.d:main.c abc.h xyz.h
这样的依赖关系表示main.o和它的依赖关系文件的依赖项是一致的,只要相关的源文件或头文件发生了改变,才会重新生成目标文件和依赖关系文件,也就达到了依赖关系文件单独更新的目的了。
虽然如此,但是这样的Makefile也不是完美的。现假设工程目录内新增一个源文件lmn.c,按照Makefile的指令make后会产生.lmn.d依赖关系文件。而如果我们再删除lmn.c源文件后,重新make后.lmn.d依然存在!尤其是当重复增删很多源文件后,工程目录下可能会存在很多无用的依赖文件,当然这些问题可以通过make clean解决。
通过前边的讨论,我们得到一个能在单目录工程下工作的通用Makefile,至于是实现为单独一个依赖文件的形式,还是每个源文件产生一个独立的依赖文件,要根据程序作者自己的喜恶来选择。虽然每种方法都有一些细微的瑕疵,但是不影响这个通用的Makefile的实用性,试想一下在工程目录下拷贝一份当前的Makefile,稍加修改便可以正确的编译开发,一定会令人心情大好。希望本文对你学习Linux写的程序开发有所帮助!