0. 前言

粉丝留言,想知道如何使用Makefile给多个文件和多级目录建立一个工程,必须安排!

关于Makefile的入门参考文章,可以先看这篇文章:

Makefile入门教程

为了让大家有个更加直观的感受,一口君将之前写的一个小项目,本篇在该项目基础上进行修改。

该项目详细设计和代码,见下文:

从0写一个《电话号码管理系统》的C入门项目【适合初学者】

一、文件

好了,开始吧!

我们将该项目的所有功能函数放到以该函数名命名的c文件,同时放到对应名称的子目录中。

比如函数allfree(),存放到 allfree/allfree.c中

最终目录结构如下图所示:

 peng@ubuntu:/mnt/hgfs/code/phone$ tree .
.
├── allfree
│ ├── allfree.c
│ └── Makefile
├── create
│ ├── create.c
│ └── Makefile
├── delete
│ ├── delete.c
│ └── Makefile
├── display
│ ├── display.c
│ └── Makefile
├── include
│ ├── Makefile
│ └── phone.h
├── init
│ ├── init.c
│ └── Makefile
├── login
│ ├── login.c
│ └── Makefile
├── main
│ ├── main.c
│ └── Makefile
├── Makefile
├── menu
│ ├── Makefile
│ └── menu.c
├── scripts
│ └── Makefile
└── search
├── Makefile
└── search.c 11 directories, 22 files

直接看下编译结果吧:

peng@ubuntu:/mnt/hgfs/code/phone$ make
make[1]: Entering directory '/mnt/hgfs/code/phone/allfree'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/allfree'
make[1]: Entering directory '/mnt/hgfs/code/phone/create'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/create'
make[1]: Entering directory '/mnt/hgfs/code/phone/delete'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/delete'
make[1]: Entering directory '/mnt/hgfs/code/phone/display'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/display'
make[1]: Entering directory '/mnt/hgfs/code/phone/init'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/init'
make[1]: Entering directory '/mnt/hgfs/code/phone/login'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/login'
make[1]: Entering directory '/mnt/hgfs/code/phone/menu'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/menu'
make[1]: Entering directory '/mnt/hgfs/code/phone/search'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/search'
make[1]: Entering directory '/mnt/hgfs/code/phone/main'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/main'
gcc -Wall -O3 -o phone allfree/*.o create/*.o delete/*.o display/*.o init/*.o login/*.o menu/*.o search/*.o main/*.o -lpthread
phone make done!

运行结果如下:

二、Makefile常用基础知识点

[0] 符号'@' '$' '$$' '-' '-n ' 的说明

  1. '@'

    通常makefile会将其执行的命令行在执行前输出到屏幕上。

    如果将‘@’添加到命令行前,这个命令将不被make回显出来。

    例如:
@echo  --compiling module----;  // 屏幕输出  --compiling module----
echo --compiling module----; // 没有@ 屏幕输出echo --compiling module----
  1. ' - '

通常删除,创建文件如果碰到文件不存在或者已经创建,那么希望忽略掉这个错误,继续执行,就可以在命令前面添加 -,

-rm dir;
-mkdir aaadir;
  1. ' $ '

    美元符号$,主要扩展打开makefile中定义的变量

  2. ' $$ '

    $$ 符号主要扩展打开makefile中定义的shell变量

[1] wildcard

说明:

列出当前目录下所有符合模式“ PATTERN”格式的文件名,并且以空格分开。“ PATTERN”使用shell可识别的通配符,包括“ ?”(单字符)、“ *”(多字符)等。

示例:

$(wildcard *.c)

返回值为当前目录下所有.c 源文件列表。

[2] patsubst

说明:把字串“ x.c.c bar.c”中以.c 结尾的单词替换成以.o 结尾的字符。

示例:

$(patsubst %.c,%.o,x.c.c bar.c)

函数的返回结果

 x.c.o bar.o

[3] notdir

说明:去除文件名中的路径信息

示例:

SRC = ( notdir ./src/a.c )

去除文件a . c 的路径信息 , 使用 (notdir ./src/a.c) 去除文件a.c的路径信息,使用 (notdir./src/a.c)去除文件a.c的路径信息,使用(SRC)得到的是不带路径的文件名称,即a.c。

[4] 包含头文件路径

使用-I+头文件路径的方式可以指定编译器的头文件的路径

示例:

INCLUDES = -I./inc
$(CC) -c $(INCLUDES) $(SRC)

[5] addsuffix

函数名称:加后缀函数—addsuffix。

语法:

$(addsuffix SUFFIX,NAMES…)

函数功能:为“NAMES…”中的每一个文件名添加后缀“SUFFIX”。参数“NAMES…”

为空格分割的文件名序列,将“SUFFIX”追加到此序列的每一个文件名

的末尾。

返回值:以单空格分割的添加了后缀“SUFFIX”的文件名序列。

函数说明:

示例:

$(addsuffix .c,foo bar)

返回值为

foo.c bar.c

[6] 包含另外一个文件:include

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。

比如命令

include file.dep

即把file.dep文件在当前Makefile文件中展开,亦即把file.dep文件的内容包含进当前Makefile文件

在 include前面可以有一些空字符,但是绝不能是[Tab]键开始。

[7] foreach

foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的

语法是:

$(foreach <var>,<list>,<text> )

这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。

每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

所以,最好是一个变量名,可以是一个表达式,而中一般会使用这个参数来依次枚举中的单词。

举例:

names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

注意,foreach中的参数是一个临时的局部变量,foreach函数执行完后,参数的变量将不在作用,其作用域只在foreach函数当中。

[8] call

“ call”函数是唯一一个可以创建定制化参数函数的引用函数。

使用这个函数可以实现对用户自己定义函数引用。

我们可以将一个变量定义为一个复杂的表达式,用“ call”函数根据不同的参数对它进行展开来获得不同的结果。

函数语法:

$(call variable,param1,param2,...)

函数功能:

在执行时,将它的参数“ param”依次赋值给临时变量“ $(1)”、“ $(2)” call 函数对参数的数目没有限制,也可以没有参数值,没有参数值的“ call”没有任何实际存在的意义。

执行时变量“ variable”被展开为在函数上下文有效的临时变量,变量定义中的“ $(1)”作为第一个参数,并将函数参数值中的第一个参数赋值给它;

变量中的“ $(2)”一样被赋值为函数的第二个参数值;

依此类推(变量$(0)代表变量“ variable”本身)。

之后对变量“ variable” 表达式的计算值。

返回值:

参数值“ param”依次替换“ $(1)”、“ $(2)”…… 之后变量“ variable”定义的表达式的计算值。

函数说明:

  1. 函数中“ variable”是一个变量名,而不是变量引用。因此,通常“ call”函数中的“ variable”中不包含“ $”(当然,除非此变量名是一个计算的变量名)。
  2. 当变量“ variable”是一个 make 内嵌的函数名时(如“ if”、“ foreach”、“ strip”等),对“ param”参数的使用需要注意,因为不合适或者不正确的参数将会导致函数的返回值难以预料。
  3. 函数中多个“ param”之间使用逗号分割。
  4. 变量“ variable”在定义时不能定义为直接展开式!只能定义为递归展开式。

函数示例:

reverse = $(2)$(1)
foo = $(call reverse,a,b)
all:
@echo "foo=$(foo)"

执行结果:

foo=ba

即a替代了$(1),b替代了$(2)

三、编译详细说明

我们在根目录下执行make命令后,详细步骤如下:

  1. include scripts/Makefile :将文件替换到当前位置,
  2. 使用默认的目标all,该目标依赖于$(Target)

    $(Target) 在scripts/Makefile中定义了,即phone
  3. $(Target)依赖于mm
  4. mm这个目标会执行
@ $(foreach n,$(Modules),$(call modules_make,$(n)))

Modules是所有的目录名字集合,

foreach 会遍历字符串$(Modules)中每个词语,

每个词语会赋值给n,

同时执行语句:

call modules_make,$(n)
  1. modules_make 被$(MAKE) -C $(1)所替代,

$(MAKE) 有默认的名字make

-C:进入子目录执行make

$(1) :是步骤4中$(n),即每一个目录名字

最终步骤4的语句就是进入到每一个目录下,执行每一个目录下的Makefile

  1. 进入某一个子目录下,执行Makefile

    默认目标是all,依赖Objs
Objs := $(patsubst %.c,%.o,$(Source))

patsubst 把字串$ource中以.c 结尾的单词替换成以.o 结尾的字符

Source := $(wildcard ./*.c)

wildcard 会列举出当前目录下所有的.c文件

所以第6步最终就是将子目录下的所有的.c文件,编译生成对应文件名的.o文件

$(CC) $(CFLAGS) -o $(Target) $(AllObjs) $(Libs)

这几个变量都在文件scripts/Makefile中定义

$(CC) :替换成gcc,制定编译器

$(CFLAGS) :替换成-Wall -O3,即编译时的优化等级

-o $(Target):生成可执行程序phone

$(AllObjs)

AllObjs := $(addsuffix /*.o,$(Modules))

addsuffix 会将 /*.o追加到$(Modules)中所有的词语后面,也就是我们之前在子目录下编译生成的所有的.o文件

$(Libs) :替换为-lpthread,即所需要的动态库

大家可以根据这个步骤,来分析一下执行make clean时,执行步骤

完整的实例程序公众号后台回复:电话号码管理

《电话号码管理-makefile版.rar》

利用Makefile给多文件、多目录C源码建立工程的更多相关文章

  1. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(32)-swfupload多文件上传[附源码]

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(32)-swfupload多文件上传[附源码] 文件上传这东西说到底有时候很痛,原来的asp.net服务器 ...

  2. swfupload多文件上传[附源码]

    swfupload多文件上传[附源码] 文件上传这东西说到底有时候很痛,原来的asp.net服务器控件提供了很简单的上传,但是有回传,还没有进度条提示.这次我们演示利用swfupload多文件上传,项 ...

  3. 开源方案搭建可离线的精美矢量切片地图服务-8.mapbox 之sprite大图图标文件生成(附源码)

    项目成果展示(所有项目文件都在阿里云的共享云虚拟主机上,访问地图可以会有点慢,请多多包涵). 01:中国地图:http://test.sharegis.cn/mapbox/html/3china.ht ...

  4. CesiumJS 2022^ 源码解读[0] - 文章目录与源码工程结构

    很高兴你能在浮躁的年代里还有兴趣阅读源代码,CesiumJS 至今已有十年以上,代码量也积累了三十多万行(未压缩状态). 我也很荣幸自己的文章能被读者看到,如果对你有帮助.有启发,点个赞就是对我最大的 ...

  5. php实现文件上传的源码

    php实现文件上传的源码,更多php技术开发就去php教程网,http://php.662p.com <?php ##author :Androidyue ##sina @androidyue ...

  6. Hadoop之HDFS原理及文件上传下载源码分析(下)

    上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文 ...

  7. arcgis api 3.x for js 地图加载多个 SHP 图层压缩以及 json 文件展示(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  8. leaflet结合geoserver利用WFS服务实现图层删除功能(附源码下载)

    前言 leaflet 入门开发系列环境知识点了解: leaflet api文档介绍,详细介绍 leaflet 每个类的函数以及属性等等 leaflet 在线例子 leaflet 插件,leaflet ...

  9. ASP.NET MVC5+EF6+EasyUI 后台管理系统(32)-swfupload多文件上传[附源码]

    系列目录 文件上传这东西说到底有时候很痛,原来的asp.net服务器控件提供了很简单的上传,但是有回传,还没有进度条提示.这次我们演示利用swfupload多文件上传,项目上文件上传是比不可少的,大家 ...

  10. 软件包管理_rpm命令管理_yum工具管理_文件归档压缩_源码包管理

    rpm命令管理软件 对于挂载的像U盘那种都会在midea目录下,但是会显示在桌面上 安装软件(i:install,v:verbose冗长的,h:human):rpm  -ivh  xxxx.rpm 安 ...

随机推荐

  1. 保护您的Web应用:使用雷池(SafeLine)WAF的入门指南

    雷池(SafeLine)是长亭科技耗时近 10 年倾情打造的 WAF,核心检测能力由智能语义分析算法驱动.旨在提供卓越的安全保护.本文将带您一步步了解如何安装.配置和测试SafeLine,以及如何利用 ...

  2. mysql+redis点赞功能剖析

    最近在一个应用上需要用到点赞的功能,因为点赞的功能比较常用,很多人看到了大拇指就点了上去,如果单单采用mysql的方式的话可以会对数据库造成很大的压力. 我看了下网上一些博主的提供的解决方案,主要以m ...

  3. python中globals()的用法

    python中globals()的用法 1. 获取所有的全局变量, 获取到的内容如下: {'__name__': '__main__', '__doc__': None, '__package__': ...

  4. Android ADB命令集锦

    Android ADB命令集锦 原文(有删改):https://blog.csdn.net/dianziagen/article/details/57400723 本文包括: adb基本指令 Shel ...

  5. 海思SDK 学习 :002-实例代码分析

    背景 需要了解 海思HI35xx平台软件开发快速入门之背景知识,为了方便测试,还需要了解 海思SDK 的安装 知识 由于海思的应用程序启动 MPP 业务前,必须完成 MPP 系统初始化工作.同理,应用 ...

  6. 时间序列分析专题——利用SPSS专家建模器进行建模

    SPSS的专家建模器可以自动识别数据,给出最适合的模型,本章通过三个例题介绍如何使用SPSS实现时间序列分析.由于本人对时间序列分析的理解尚浅,做出模型后在论文上的呈现形式需要取查阅资料,以便更好地在 ...

  7. Luogu P5745 区间最大和

    原题链接:Luogu P5745 区间最大和 初看这道题,

  8. Grafana Loki查询加速:如何在不添加资源的前提下提升查询速度

    Grafana Loki查询加速:如何在不添加资源的前提下提升查询速度 来自Grafana Loki query acceleration: How we sped up queries withou ...

  9. django 计算两个TimeField的时差

    在 Django 中,你可以使用 datetime 模块来计算两个 TimeField 字段的时间差.以下是一个示例: from datetime import datetime, timedelta ...

  10. CF479C 题解

    洛谷链接&CF 链接 题目简述 一个人想要安排期末考试的时间. 有 \(n\) 场考试,每场考试有两个时间 \(x_i,y_i\),一个是老师规定的时间,另外一个是他与老师商量好的考试时间. ...