Makefile学习总结

Makefile用法分析

   

在linux开发中,应用程序的编译基本都采用GNU的make工具,而make搭配Makefile来实现工程代码的编译,在越是大型复杂的项目中,make的强悍之处越是明显。在使用了一段时间make后,对其用法进行分析。本文是在学习了陈皓的“跟我一起学Makefile”文章后,对自己学习的小结。

一.简单的例子

现在,我们有一个C++的项目需要进行编译,项目包含3个头文件,3个.cpp文件,分别是main.cpp,main.h,read.cpp,read.h,write.cpp,write.h,下面就是我们的Makefile了:

我们需要生成的目标是可执行文件test,test由中间目标文件.o链接生成,.o文件根据依赖关系由相应的头文件和.cpp源文件编译而成,伪目标clean用于清除目标文件和.o文件。

在我的Makefile中,我们发现编译工具cc即使不指明为g++也可以正常编译,但是如果是在目标文件链接时不指明的话,会有提示找不到相关的库,那关于这个cc到底是根据什么来选择编译工具的呢?通过查阅资料和测试发现,编译程序cc一般默认的都是gcc,当我们对源文件编译时,对于.c文件,gcc和g++分别识别为c和c++程序,对于.cpp文件,两者都会识别为c++程序,所以在编译时我们完全可以用默认cc来编译.c和.cpp文件(当然对于其他平台或嵌入式中还是要指定对应的编译工具,防止出错)。但是链接时,需要对.o中间文件链接库生成可执行程序,g++会链接c++的库,gcc链接c的库,所以如果链接时需要指明编译器。在平常使用中,考虑到的易维护和防出错,都是统一将编译和链接的编译器都指明为g++。

好,通过这个Makefile实现了我们的功能需求,但是我们发现,这种Makefile虽然逻辑简单,但是一旦当项目体积非常大时,写Makefile会是一件非常痛苦的事,需要写几百个中间目标文件的依赖关系,OK,别担心,GNU的make很强大,它可以通过隐晦规则进行自动推导。

二.隐晦规则和自动推导

GNU的make工具存在隐晦规则,只要make看到一个.o文件,他会默认把对应的.c或.cpp(后面讨论时会都用.c文件来讨论)文件加到依赖关系中,比如如果有一个main.o,那么他会默认把main.c加到其依赖关系中,并且make会对其进行自动推导,包括文件的依赖关系和后面的命令。那么,我们又可以得到一个新的Makefile了。

这样我们得到了一个看起来更简洁的Makefile,我们不用书写每个中间目标文件的编译指令了,因为make会自动推导,但是这样貌似只是部分降低了我们的劳动力,对于几百个源文件的程序,我们还是需要写出来每个文件隐晦规则之外的依赖关系,虽然不用写编译指令,但还是非常多,而且这样的Makefile通用性并不高,如果我们写了个其他的测试程序,这个Makefile我们还是需要更改许多,OK,make这个工具的静态模式和自动变量可以帮我们解决这个问题。

三.静态模式和自动变量

Make中的静态模式的语法如下:

<targets ...>: <target-pattern>: <prereq-patterns ...>

<commands>

....

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern是指明了targets的模式,也就是的目标集模式。

prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

举个例子,对于前面的objects目标集合,<target-parrtern>定义成“%.o”,意思是我们的<target>集合中都是以“.o”结尾的,而如果我们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。如main.o通过模式取到的依赖目标就是main.c文件。静态模式帮助我们完成将几百个源文件用一种通用的模式来代表,我们可以不用写出所有中间文件的依赖关系,只要用一个静态模式就OK了。

接下来有个问题,在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。这就需要make 的自动化变量。

Make的自动化变量。 所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

下面是所有的自动化变量及其说明:

[email protected]

表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"[email protected]"就是匹配于

目标中模式定义的集合。

$%

仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a (bar.o)",那么,"$%"就是"bar.o","[email protected]"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

$<

依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

$?

所有比目标新的依赖目标的集合。以空格分隔。

$^

所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$+

这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

$*

这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的, 很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。

更新后的Makefile如下:

OK,写到这里,我们的Makefile已经非常非常简洁了,但是这里面存在两个问题,第一个问题是,我们所有的目标文件的依赖关系都只有相应的源文件,并没有其他所需头文件的依赖关系,比如main.o的依赖关系包含了main.cpp,main.h,read.h,write.h,在首次编译时,依赖关系会找到相应的相应的头文件,但是之后如果修改了某个头文件,则再次编译头文件并不会更新,所以这种方式中缺少头文件的依赖关系,一种解决方法是每次重新编译时,先清空之前的编译目标文件就OK了,还有一种方法是建立.c文件的依赖关系(下次再写)。

第二个问题是,我们发现,我们还是要写出所有的目标文件集,同样的,对于几百个中间文件的项目来说,这也是很头疼的事,我们可以通过通配符和一些函数来解决这个问题。

我们的需求是让make自己找到当前目录下所有的源文件,并且对这些源文件根据依赖关系进行编译处理,实现完全的自动化。首先我们需要用通配符*来找到所有的源文件,并对应生成所有源文件对应的中间目标文件,这主要通过patsubst函数来实现,该函数是模式字符串替换函数(对于所有的函数可以查询后文的函数表),帮我们将依赖目标集的所有源文件对应生成.o文件。

Makefile写到这里,应该是简洁性和通用性都比较高的了,但是,现在的makefile在实际中可用性不高,因为实际项目中源文件往往放置在多个文件夹中,而且还会设置许多编译参数,所以下面将会写一个体现源文件结构复杂度和编译器参数的makefile。

四.编译器参数

现在,我们假设,我们的read.cpp,read.h在当前目录的read目录下,write.cpp和write.h在当前目录下的write目录下,那么我们新的makefile又出炉了。

原有的wildcard是匹配当前目录下所有的源文件,但是有多个文件夹,就无法匹配到了,于是在这里我们就可以使用VPATH这个变量(这个make默认识别的变量用于在当前目录下查找文件查找不到时,默认到VPATH路径下查找),同时还需要使用一个函数forreach。由于这里只匹配了源文件,相应的头文件的位置没有包含,所以还需要包含一下头文件。

OK,到这里,貌似我们已经写出了一个比较好的Makefile,但是,实际的编译参数我们都还没涉及,下面就具体分析一下,GNU下的编译工具的编译参数。

五.链接库

有时我们需要链接第三方库,那么如何链接库,如何使用库中的函数?

首先,在源代码中,我们需要包含所使用的库的接口函数的头文件;

其次,在Makefile中,我们需要在头文件和库的搜索路径中分别加入所需头文件和库的路径。假设库和库的头文件在路径/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209路径下,那么包含方式

头文件:-I/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209    //加入头文件路路径

库:-L/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209  //加入库文件路径

如果库的名字是libminiupnpc.a,那么在链接时的参数就是 -lminiupnpc

类似于下图:

结束。

时间: 2024-10-09 20:58:00

Makefile学习总结的相关文章

【Linux学习】Makefile学习(二)

FatMouse' Trade Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 40975    Accepted Submission(s): 13563 Problem Description FatMouse prepared M pounds of cat food, ready to trade with the cats g

MakeFile 学习

MakeFile 学习 1.静态模式 objects = foo.o bar.o all: $(objects) $(CC) $(CFLAGS) -o -o [email protected] $^ $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o [email protected] 上面的例子中,指明了我们的目标从$object中获取,"%.o"表明要所有以".o"结尾的目标,也就是"foo.o bar.o

[转]Windows平台下Makefile学习笔记

Windows平台下Makefile学习笔记(一) 作者:朱金灿 来源:http://blog.csdn.net/clever101 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译平台的问题(发现一些开源代码已经在使用VS2010开发,但我还没安装VS2010,我想在VS2008下编译这些代码):另一方面源码在服务器端编译的话,使用IDE的方式编译还是不太方便. 本文主要分为三部分:第一部分讲述namke工具使用makefile的用法:第二部分讲述makefile的主要语法:

Makefile学习教程 跟我一起写 Makefile

https://github.com/zhangliyong/myknowledge/blob/master/Makefile%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B%20%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99%20Makefile.wiki ++ Makefile学习教程 跟我一起写 Makefile 0 Makefile概述 什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Wi

Makefile 学习&amp;binutils工具集,软件开发利器

gcc -E 能有效的帮助我们解决与宏有关的编译错误: gcc -S能获得一个C源程序文件的汇编程序: gcc -v能获得系统头文件的路径 生成依赖关系: gcc -M gcc -MM 生成不包含系统头文件的依赖关系 指定链接库: gcc -L:告诉gcc可以从哪个目录查找库文件: gcc -l :用于告诉gcc在生成可执行程序时需要链接的库名: 一.静态库生成步骤如下:步骤一:生成 str_out.o 目标文件gcc -c str_out.c注:不加 -o 则生成同名的 *.o 目标文件(st

gcc gdb makefile学习笔记

一.gcc (GNU C compiler )1.预处理  gcc -E aaa.c  -o aaa.i   →  .i(c代码)    ↓ 2. 编译   gcc -S aaa.i  -o aaa.s   →   .s(汇编代码)    ↓3. 汇编   gcc -c  aaa.s  -o aaa.o  →   .o(目标代码-二进制)    ↓4. 链接   gcc aaa.o    -o aaa    →   aaa(可执行文件) 优化编译链接   gcc -O(O2/O3) aaa.o

makefile 学习(一)

1. 使用 makefile 的原因 假如每次都手动输入, 可能会写出这样的编译命令 ? 1 2 3 4 5 6 7 gcc -c main.c gcc -c mytool1.c gcc -c mytool2.c gcc -o main main.o mytool1.o mytool2.o 那么, 可不可以将这个命令写成 shell 脚本呢, 这样就不需要费事打命令了 但是, 使用脚本有一个弊端, 假设只有一个源文件被修改, 那么 shell 命令会把整个项目重新编译一遍 所以, 为了简化编译同

makefile 学习一

最近在学习nginx,由于实在linux下,一些代码需要用makefile文件来编译,比较节省时间.因为在nginx中添加一个新的模块如果用./configure方法来添加,特别是当你的代码有错时,修改以后又./configure,那么没编译一次都需要几分钟,实现在受不了了,就学习一下makefile,另一个原因是自己以前没有接触过linux,跟没有在linux下编写过代码,这次决定在学nginx的同时学习一个linux编程,当然就有必要学习一下makefile(按需求学习,重点放在学习ngin

【原创】MakeFile学习成果之模板

花了一个早上,看MakeFile的文档.参考例子,自己写了一个相对通用的MakeFile模板.特放上来,以防丢失.刚学习MakeFile的朋友可以参考看看,高手就当路过了!! 先看目录结构!! ls -R /************************************************.:cc.lst exe/  inc/  inc2/  Makefile  obj/  src/  src2/ ./exe:ftptest* ./inc:ftplib.h ./inc2:qftp.