Makefile实例,利用Makefile给多文件、多目录C源码建立工程

2023-11-12

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 替 代 了 (1),b替代了 (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时,执行步骤

完整的实例程序:
链接:https://pan.baidu.com/s/1aLTsh6D7CXIDqgC9kMtCdA 提取码:57n6
《电话号码管理-makefile版.rar》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Bash 脚本监听按键以继续

    因此 我想编写一个由一系列步骤组成的 bash 脚本 并将其标识为 task 然而 每个步骤都只能完成并且可以根据用户的需要运行 Do task1 if keypressed stop task1 and move on this is t
  • 这种 bash 文件名提取技术有何用途?

    我有一部分 bash 脚本正在获取不带扩展名的文件名 但我试图了解这里到底发生了什么 是做什么用的 有人可以详细说明 bash 在幕后做了什么吗 如何在一般基础上使用该技术 bin bash for src in tif do txt sr
  • 为什么 shell=True 的 subprocess.Popen() 在 Linux 和 Windows 上的工作方式不同?

    使用时subprocess Popen args shell True 跑步 gcc version 仅作为示例 在 Windows 上我们得到 gt gt gt from subprocess import Popen gt gt gt
  • 如何使用 bash 中提供的工具生成一系列非周末日期?

    我想生成一个文件列表 其中名称包含 filename date 例如file 20111101 file 20120703 开始November 1 2011直到今天 应该不包括周末 Thanks 2011年试试这个 for y in 20
  • 使用 grep 查找所有匹配的模式

    In txt1 S01A1P2 S01A1P5 S01A1P4 In txt2 data train wave S01A1P3 mfc data train wave S01A1P7 mfc data train wave S01A1P8
  • PHP exec rm -Rf 不适用于子目录

    我试图删除特定文件夹中的所有内容 但它似乎不会影响子文件夹 但它应该 因为 bash 命令是从控制台执行的 system rm Rf some dir 该命令中不需要星号 如果要与文件一起删除目录 请同时删除斜杠 留下斜杠将删除文件 但保留
  • 在 Shell 中提取匹配模式后的字符串

    如何提取 Shell 脚本中匹配模式后面的任何字符串 我知道 Perl 脚本中的此功能 但不知道 Shell 脚本中的功能 以下是示例 subject 01 这是一个示例主题 可能会有所不同 我必须提取 Subject 01 后面的任何字符
  • Bash 的源命令无法处理从互联网上卷曲的文件

    我正在尝试使用curl从互联网获取脚本文件 如下所示 source lt curl url echo done 我看到的是 完成 得到了回响before卷曲甚至开始下载文件 这是实际的命令和输出 bash 3 2 source lt cur
  • 在 shell/shell 脚本中设置 MongoDB 写关注

    我正在尝试填充一个集合MongoDB的壳 据我了解 使用轻松的Write Concern可以大大加快这个过程 我说的是文档 http docs mongodb org manual core write concern write oper
  • 是否可以从应用程序执行 ADB shell 命令?

    我有一个安卓电脑 http www timingpower com rk3288 with root 开箱即用 连接到始终以横向显示的外部显示器 HDMI 和 USB 即使我的应用程序在清单中的活动声明中指定纵向 android scree
  • HBase Shell 日志记录

    使用 HBase shell 时 我收到大量日志记录 包括 INFO 和 DEBUG 消息 虽然这对于学习 HBase 内部结构来说很有趣 但它非常冗长并且可能会掩盖输出 我尝试过以多种不同的方式更改日志记录级别 包括所描述的here ht
  • Unix shell脚本找出脚本文件所在的目录?

    基本上我需要使用与 shell 脚本文件位置相关的路径运行脚本 如何将当前目录更改为与脚本文件所在的目录相同 在 Bash 中 你应该得到你需要的东西 如下所示 usr bin env bash BASEDIR dirname 0 echo
  • 使用 hcitool 扫描低功耗蓝牙?

    当我运行此命令时 BLE 设备扫描仅持续 5 秒 sudo timeout 5s hcitool i hci0 lescan 输出显示在终端屏幕中 但是 当我将输出重定向到文件以保存广告设备的地址时 每次运行该命令时 我都会发现该文件是空的
  • 带变量的 AWK 负正则表达式

    我在 bash 脚本中使用 awk 来比较两个文件以获取不匹配的行 我需要将第二个文件的所有三个字段 作为一个模式 与第一个文件的所有行进行比较 第一个文件 chr1 9997 10330 HumanGM18558 peak 1 150 1
  • 如何使用 exec.Command 在 golang 中执行 Mysql 脚本

    您好 我正在尝试执行一个脚本以使用 Golang 将数据填充到数据库中 func executeTestScript cmd exec Command usr local mysql bin mysql h127 0 0 1 P3333 u
  • 如何使用ansible运行询问用户输入的脚本?

    我想使用 ansible 运行 shell 脚本 但 shell 脚本需要用户输入才能成功执行 例如 我的 shell 脚本询问唯一的 idossec agent 通过ansible我可以预定义我的unique id user input
  • 如何按文件大小对查找结果进行排序

    如何按文件大小对 find 命令的结果进行排序 我试图对这个 find 命令的结果进行排序 find src type f print0 我不需要目录的大小 我需要仅按大小排序的文件相对路径 这是如何做的using find command
  • 从 shell 脚本导入函数

    我有一个 shell 脚本 我想用 shUnit 测试它 该脚本 以及所有函数 位于单个文件中 因为它使安装更加容易 示例script sh bin sh foo bar code 我想编写第二个文件 不需要分发和安装 来测试中定义的函数s
  • bash.sh 运行 cron 的权限被拒绝

    如何在这里使用 bash 脚本运行 cron 我做了如下操作 这里有错误 我想知道如何在 ubuntu 中做到这一点 我现在对它感到震惊 bash sh 文件 bin bash cd var www Controller usr bin p
  • 在 shell 脚本中将脚本目录更改为用户的 homedir

    在我的 bash 脚本中 我需要将当前目录更改为用户的主目录 如果我想更改为用户的foo主目录 从命令行我可以执行以下操作 cd foo 效果很好 但是当我从script它告诉我 bar sh line 4 cd foo No such f

随机推荐

  • Adaboost

    基本原理 基本原理就是将多个弱分类器结合 形成一个强分类器 Adaboost采用迭代的思想 每次迭代只训练一个弱分类器 训练好的弱分类器将参与下一次迭代的使用 也就是说 在第N次迭代中 一共就有N个弱分类器 其中N 1个是以前训练好的 其各
  • 使用ctypes模块进行键盘钩取

    原理 使用user32 dll提供的SetWindowsHookExA函数 可以设置钩子 当有消息到来或发生鼠标 键盘输入事件时 操作系统提供了中间拦截机制 这称为 钩子 从功能上实现这种机制的函数称为钩子过程 回调函数 操作系统支持为一个
  • GPT垂直领域相关模型 现有的开源领域大模型

    对于ToC端来说 广大群众的口味已经被ChatGPT给养叼了 市场基本上被ChatGPT吃的干干净净 虽然国内大厂在紧追不舍 但目前绝大多数都还在实行内测机制 大概率是不会广泛开放的 毕竟 各大厂还是主盯ToB ToG市场的 从华为在WAI
  • excel 中使用vlookup函数

    vlookup函数使用方法 https zhuanlan zhihu com p 29161495 使用函数后不显示只显示公式处理办法 第二点 将函数所在单元格的格式改为常规或数值格式 并点击F2或者点击一下编辑栏 再点击Enter即可 h
  • 微软又赢麻了!联合 Meta 发布免费商业应用的开源 AI 模型 Llama 2

    整理 屠敏 出品 CSDN ID CSDNnews 昔日的竞争对手 今日的合作盟友 忽如一夜春风来 开源大模型迎来新局面 今天是 OSS AI 胜利的一天 随着 Meta 最新发布一个新的开源 AI 模型 Llama 2 网上盛赞的声音不绝
  • 10月5日 大数据专题

    10月5日 大数据专题 中秋国庆双节盛典 大数据 大数据 big data IT行业术语 是指无法在一定时间范围内用常规软件工具进行捕捉 管理和处理的数据集合 是需要新处理模式才能具有更强的决策力 洞察发现力和流程优化能力的海量 高增长率和
  • 笔记整理nodeJS

    nodeJS 学习方法 掌握思想 编程思想很重要 语言只是工具 不仅仅只是记住了API 查资料的方式 API文档 1 搭建服务器 2 mongodb 用 注册和登录增删改查新闻 bootstrap 3 api server 注册和登录增删改
  • CSS学习(三)CSS优先级和盒子模型

    优先级的介绍 特性 不同选择器具有不同的优先级 优先级高的选择器样式会覆盖优先级低选择器样式 优先级公式 继承 lt 通配符选择器 lt 标签选择器 lt 类选择器 lt id选择器 lt 行内样式 lt important 注意点 1 i
  • Anaconda的安装

    个人简介 作者简介 大家好 我是W chuanqi 一个编程爱好者 个人主页 W chaunqi 支持我 点赞 收藏 留言 愿你我共勉 若身在泥潭 心也在泥潭 则满眼望去均是泥潭 若身在泥潭 而心系鲲鹏 则能见九万里天地 文章目录 Anac
  • Bootstrap Navbar

    Bootstrap Navbar 导航栏 是Bootstrap框架中一个重要的组件 用于创建响应式的导航菜单 适用于各种屏幕大小和设备 导航栏通常位于网页的顶部 为用户提供导航和链接到不同页面或功能 以下是Bootstrap Navbar的
  • 第十二届蓝桥杯国赛试题及解析

    第一题 选择题严禁使用程序验证设s HiLanQiao 运行以下哪个选项代码可以输出 LanQiao 子串 A A print s 7 B print s 6 11 C print s1 7 01 D print s 7 1 第二题 选择题
  • eclipse配置tomcat

    eclipse环境下如何配置tomcat 打开Eclipse 单击 Window 菜单 选择下方的 Preferences 单击 Server 选项 选择下方的 Runtime Environments 点击 Add 添加Tomcat 点击
  • unityShader之固定渲染管线

    固定渲染管线功能较单一 能实现的效果不多 基本快要被淘汰了 老式的机器上还能用一用 附上几个脚本示例简单说明一下 shader likang king01 properties Color MainColor color 0 0 0 1 固
  • 【C#学习笔记】指针使用

    using System namespace ConsoleApplication2 class Program static void Main string args int a 5 unsafe int pa a Console Wr
  • 浅谈边缘计算

    一 概念 定义 1 维基百科对边缘计算的定义如下 边缘计算是一种优化云计算系统的方法 在边缘执行分析和知识生成减少受控系统和数据中心之间的通信带宽 2 OpenStack基金会对边缘计算的定义如下 边缘计算是为应用开发者和服务提供商在网络的
  • 【LeetCode102】二叉树的层序遍历

    题目描述 首刷自解 vector
  • 【Vue 常用属性】

    Vue 常用属性 vue常用的属性有 数据属性 方法 计算属性 监听属性 数据属性 组件的data 选项是一个函数 Vue 会在创建新组件实例的过程中调用此函数 它应该返回一个对象 然后Vue 会通过响应性系统将其包裹起来 并以 data
  • JDK8特性--Stream(求和,过滤,排序)

    Stream简介 Java 8 API添加了一个新的抽象称为流Stream 可以让你以一种声明的方式处理数据 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象 元素流在管道
  • Modern OpenGL---09 纹理(纹理单元可贴多个纹理)

    对之前画的矩形基础上贴上纹理 在片段着色器中 声明一个采样器 表示纹理位置 每个纹理位置叫做纹理单元 比如 0 1等 当只有一个纹理事时 纹理单元默认为0 当有一个以上的纹理时 则需要通过uniform从外部设置每个纹理单元的值 需要注意的
  • Makefile实例,利用Makefile给多文件、多目录C源码建立工程

    0 前言 粉丝留言 想知道如何使用Makefile给多个文件和多级目录建立一个工程 必须安排 关于Makefile的入门参考文章 可以先看这篇文章 Makefile入门教程 为了让大家有个更加直观的感受 一口君将之前写的一个小项目 本篇在该