【Makefile通用模板】入门必看篇,超详细

news2024/9/19 18:18:35

工程目录

假如我们有以下目录结构:

.
├── inc
│   ├── add.h
│   └── sub.h
├── main.c
└── src
    ├── add.c
    └── sub.c

文件中的内容如下:

//main.c
#include <stdio.h>
#include "add.h"
#include "sub.h"

int main()
{
    int x = 9;
    printf("x = %d\n", add_one(x));
    printf("x = %d\n", sub_one(x));
    return 0;
}

//add.h
int add_one(int x);

//add.c
int add_one(int x)
{
    return x + 1;
}

//sub.h
int sub_one(int x);

//sub.c
int sub_one(int x)
{
    return x - 1;
}

对于上述这样的多.c文件,又不在同一个目录下的大型工程中,借助makefile可以来减轻工作任务
(上述是一个很小很小的工程)

准备工作

在使用gcc 将 源文件 main.c编译成 可执行目标程序 总共需要4步:

平常在编译项目时,预处理与编译器这两步会省略,是先将源文件 .c 编译成 .o 文件,然后再链接 .o 文件

gcc -c main.c -o main.o
gcc main.o -o main.exe/main.out

编写Makefile

接下来会一步一步的编写一个Makefile文件,这个文件可以适配于大部分C/C++工程,让我们开始吧!

1. 定义可执行文件名、GCC类型

先定义一个最终可执行文件名的变量:

TARGET = main

变量值可以随意定义。

gcc分为很多种,常见的有:gcc、arm-linux-gcc、arm-none-eabi-gcc等等,所以为了Makefile适配更多的C/C++项目,可以将编译器定义一个变量,这后续更改起来很方便。我这里使用的gcc:

CC = gcc

2. 中间文件的路径的变量

由前文可知,在编译过程中会编译出很多的 .o 文件,一般将这些编译过程中产生的文件单独放到一个文件夹下,文件夹的名字大多叫做 build ,定义一个变量 BUILD_DIR 该变量的值就是build,用来存放中间产物,在后续编译过程中会用到:

BUILD_DIR = build

3、.c 源文件的路径

事先需要将工程中所用到的源文件 .c 的路径,这样在后续中就可直接得到 .c 文件,定义一个变量 SRC_DIR 来存放源文件 .c 的路径

SRC_DIR = \
	./    \
	./src

4、 头文件的路径

接着得到所有用到的头文件路径:

INC_DIR = \
	./inc

这gcc选项中有这个参数 -I 是告诉编译器头文件的路径,在后续中会使用Makefile的一个函数为每个头文件路径添加 -I

5、为头文件路径添加 -I

当所引用的头文件与源文件不在同一级目录下时需要添加 -I 选项指定头文件路径,在第四步中已经获取到头文件的路径,下面借助一个Makefile中的一个函数在每个头文件前面添加 -I

首先看一下函数 patsubst 的介绍。


$(patsubst <pattern>,<replacement>,<text>)
  • 名称:模式字符串替换函数。
  • 功能:查找 <text> 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换。这里, <pattern> 可以包括通配符 % ,表示任意长度的字串。如果 <replacement> 中也包含 % ,那么, <replacement> 中的这个 % 将是 <pattern> 中的那个 % 所代表的字串。(可以用 \ 来转义,以 \% 来表示真实含义的 % 字符)

  • 返回:函数返回被替换过后的字符串。

  • 示例:

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

    把字串 x.c.c、bar.c 符合模式 %.c 的单词替换成 %.o ,返回结果是 x.c.o bar.o-


定义一个变量 INCLUDE

INCLUDE	= $(patsubst %, -I %, $(INC_DIR))

这样就会在每个头文件路径前加入 -I 了。

6、得到带路径的源文件

在第三步中,我们得到了 .c 文件的存放路径,这一步我们得到带有路径的 .c 文件,简单来说就是,假如在src目录下有一个foo.c的文件,在第3步中只得到了 src 这个目录,这一步得到的是 src/foo.c

得到目录下的 .c 文件需要用到Makefile中的两个函数,foreach函数wildcard 函数


1、wildcard 函数

$(wildcard PATTERN...)

在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表

2、foreach函数

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

这个函数的意思是,把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中,然后再执行 <text> 所包含的表达式。每一次 <text> 会返回一个字符串,循环过程中, <text> 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, <text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

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

举个例子:

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 。


使用这两个函数得到带路径的 .c 文件

CFILES := $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))

7. 得到不带路径的 .c 文件

在上一步中我们得到了带路径的 .c 文件,这步借助Makefile中的函数 notdir 将路劲去除,得到 “真正的.c”


notdir 函数

$(notdir <names...>)
  • 名称:取文件函数——notdir。

  • 功能:从文件名序列 <names> 中取出非目录部分。非目录部分是指最后一个反斜杠( / )之后的部分。

  • 返回:返回文件名序列 <names> 的非目录部分。

  • 示例:

    $(notdir src/foo.c hacks)
    

    返回值是 foo.c hacks 。


定义一个变量 CFILENDIR 来存放不带路径的 .c 文件:

CFILENDIR := $(notdir  $(CFILES))

8. 将工程中的.c 文件替换成 ./build 目录下对应的目标文件 .o

这一步只是简单的字符串进行替换,对原文件不进行任何操作。我们可以先写一个伪目标,打印一下变量 CFILENDIR 的内容

# 打印结束后可以删除
print:
	@echo $(CFILENDIR)

使用 make 查看一下输出结果,会得到字符串:main.c add.c sub.c

在前面讲过编译时会在 build 目录下得到.o文件,这个.o 文件就是由.c文件生成的,因此 main.c add.c sub.c 会对应于 build 目录下的 main.o add.o sub.o

由于现在不是编译阶段,我们只对字符串进行个简单的替换操作,定义一个变量 COBJS 用来存放目录 build 下的 .o 文件

COBJS = $(patsubst %, ./$(BUILD_DIR)/%, $(patsubst %.c, %.o, $(CFILENDIR)))

此时变量 COBJS 的值就是:./build/main.o ./build/add.o ./build/sub.o


到目前为止已经得到了工程中的源文件 CFILENDIR、可重定位目标文件 COBJS 以及带有 -I 前缀的头文件路径 INCLUDE,注意,到目前为止我们操作的只是字符串而已,还未对源文件做任何操作。


9、搜索源文件

在我们这个工程中,有两个目录下存放着 .c 文件,当make需要去找寻文件的依赖关系时,可以使用变量 VPATH 让make在自动在这两个目录中去找依赖文件。

VPATH = $(SRC_DIR)

10、生成可重定位目标文件(编译阶段)

$(COBJS) : $(BUILD_DIR)/%.o : %.c
	@mkdir -p $(BUILD_DIR)
	$(CC) $(INCLUDE) -c -o $@ $<

会将源文件 .c 编译成可重定位目标文件 .o

11、链接 .o 文件

此步骤是最后一步,将所有的 .o 文件链接成可执行程序

可执行文件可以生成到指定的目录下,我这里生成到了 build 目录下

$(BUILD_DIR)/$(TARGET).exe : $(COBJS)
	$(CC) -o $@ $^

此时,Makefile 已经编写完成。

当执行make 时,发现并不是预期的目标,只执行了一句指令:

gcc  -I ./inc -c -o build/main.o main.c

这是因为make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件,如是依赖存在编译成功后就会退出执行,若是没有找到依赖,则会报错并退出。

当想达到预期的目标,共有两种办法:

第一种:将目标$(BUILD_DIR)/$(TARGET).exe 写在目标 $(COBJS) 的前面,这样就可以达到预期的结果了。

第二种:使用关键字 all ,写在关键字 all 后面的目标都会执行一次,直到所有目标执行完成,或者某个目标不成立。

此时,再执行make,就能得到预期的结果了

在这里插入图片描述

12、清理目标

make编译之后会在工程中多出很多目标文件*.o,可以写一个目标 clean 用来删除工程中的目标文件

clean:
	rm -rf $(BUILD_DIR)

Makefile全部内容:

# 可执行文件名
TARGET = main

# gcc类型
CC = gcc

# 存放中间文件的路径
BUILD_DIR = build

#存放.c 源文件的文件夹
SRC_DIR = \
	./    \
	./src

# 存放头文件的文件夹
INC_DIR = \
	./inc

# 在头文件路径前面加入-I
INCLUDE	= $(patsubst %, -I %, $(INC_DIR))

# 得到带路径的 .c 文件
CFILES := $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))

# 得到不带路径的 .c 文件
CFILENDIR := $(notdir  $(CFILES))

# 将工程中的.c 文件替换成 ./build 目录下对应的目标文件 .o
COBJS = $(patsubst %, ./$(BUILD_DIR)/%, $(patsubst %.c, %.o, $(CFILENDIR)))

# make 自动在源文件目录下搜索 .c 文件
VPATH = $(SRC_DIR)

$(BUILD_DIR)/$(TARGET).exe : $(COBJS)
	$(CC) -o $@ $^

$(COBJS) : $(BUILD_DIR)/%.o : %.c
	@mkdir -p $(BUILD_DIR)
	$(CC) $(INCLUDE) -c -o $@ $<

clean:
	rm -rf $(BUILD_DIR)

至此,Makefile通用模板已经编写完成,文章中若有错误的地方请在评论区指出~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nodejs+vue 智慧餐厅点餐餐桌预订系统

现在社会的生活节奏越来越快&#xff0c;人们对互联网的需求也越来越大&#xff0c;不仅要求使用方便&#xff0c;而且对于功能及扩展性也有了更高的要求&#xff0c;最能达到要求莫过于利用计算机网络&#xff0c;将所需功能要求和计算机网络结合起来&#xff0c;就形成了本智…

FOSSASIA Summit 的参会为 openEuler 全球化注入强心剂

2023年4月15日&#xff0c;亚洲顶级开源盛会FOSSASIA Summit 2023在新加坡落幕。openEuler作为白金赞助级别参会。 自2009年成立以来&#xff0c;除因疫情中断3年之外&#xff0c;FOSSASIA Summit已累计举办11年。作为亚洲年度开源技术旗舰活动&#xff0c;FOSSASIA Summit吸引…

C/C++ 常见编译器说明

文章目录 window下常用的编译器如何获取MSVCMinGW Linux和MaxOS下的编译器gcc和g的区别 window下常用的编译器 window下并不提供原生的类似gcc/g的类unix系统下的C/C编译器。常用的是 MSVC&#xff08;Microsoft Visual C/C&#xff09;编译器&#xff0c;在我们安装完visual …

python 获取脚本所在存储目录

获取.py文件所在目录 问题背景问题原因解决方法 问题背景 项目需要使用到当前脚本所在的目录然后保存文件 我像之前一样&#xff0c;使用了os.getcwd() 去获取脚本目录&#xff0c;保存文件&#xff0c;程序正常运行&#xff0c;但设定路径下没有任何文件&#xff0c;没有头脑…

读 AI学者生存策略

链接&#xff1a;https://arxiv.org/pdf/2304.06035.pdf 作者&#xff1a;Julian Togelius and Georgios N. Yannakakis 随着大模型 和 大数据的出现&#xff0c; AI研究者 都会感到焦虑。 没有计算资源 &#xff0c;没有标注的人力&#xff0c;很难做出突破性的研究。即使很多…

FFmpeg 中 RTSP推流桌面和Android设备延时测试

文章目录 1. FFMPEG 推流&#xff1a;1.1 FFmpeg 源码准备1.2 RTSP 推流服务器 2. 执行流程2.1 启动服务器2.2 执行桌面推流2.3 播放 3. 安卓测试 1. FFMPEG 推流&#xff1a; 1.1 FFmpeg 源码准备 官网&#xff0c;GitHub&#xff0c;CSDN中选一个就好&#xff1a; ● 官网…

在线图片编辑网站汇总

目录 前言一、在线抠图工具1. 佐糖2. remove.bg3. Clipping Magic4. 起兮深深5. BgSub6. 改图神器7. PIXLR 二、其他工具1. 压缩图2. AI人工智能图片放大 前言 本文收录了多个可以在线抠图、在线编辑图片的网站。 一、在线抠图工具 1. 佐糖 官网&#xff1a;链接 特点&#x…

一本通 3.4.4 并查集

1346&#xff1a;【例4-7】亲戚(relation) 【题目描述】 或许你并不知道&#xff0c;你的某个朋友是你的亲戚。他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。如果能得到完整的家谱&#xff0c;判断两个人是否是亲戚应该是可行的&#xff0c;但如果两个人的最近公共祖…

企业想注销境外投资备案应该怎么做?

我们今天就来说一下关于境外投资备案的注销&#xff0c;有办理的需求&#xff0c;当然就有注销的需求。如果您当初想去海外投资并购一家公司&#xff0c;因此办理了境外投资备案&#xff0c;但是由于种种原因可能没有办法投资下去了&#xff0c;那么这个时候我们就需要做境外投…

【Linux网络服务】SSH远程访问及控制

一、openssh服务器 1.1ssh协议 SSH&#xff08;Secure Shell&#xff09;是一种安全通道协议&#xff0c;主要用来实现字符界面的远程登录、远程 复制等功能&#xff1b; SSH 协议对通信双方的数据传输进行了加密处理&#xff0c;其中包括用户登录时输入的用户口令&#xff1…

测试月入30K,Soeasy?测试这一行涨薪机制被我摸透了...

1.软件测试如何实现涨薪 首先涨薪并不是从8000涨到9000这种涨薪&#xff0c;而是从8000涨到15K加到25K的涨薪。基本上三年之内就可以实现。 如果我们只是普通的有应届毕业生或者是普通本科那我们就只能从小公司开始慢慢往上走。 有些同学想去做测试&#xff0c;是希望能够日…

nodejs+vue 智能餐厅菜品厨位分配管理系统

系统功能主要介绍以下几点&#xff1a; 本智能餐厅管理系统主要包括三大功能模块&#xff0c;即用户功能模块和管理员功能模块、厨房功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户是管理员&#xff0c;管理员登录后&#xff0c;通过管理员功能来…

BPF之前端工具BCC与bpftrace

BPF前端工具BCC与bpftrace 一、概述 BCC和bpftrace到底是什么&#xff0c;与BPF是什么关系呢&#xff1f; 经过上一篇的介绍&#xff0c;BPF是内核中的执行引擎&#xff0c;BCC和bpftrace则是两个前端工具&#xff0c;比如用户可以直接使用的命令行工具。 BCC与bpftrace又有…

Stable Diffusion Lora模型训练详细教程

1. 介绍 通过Lora小模型可以控制很多特定场景的内容生成。 但是那些模型是别人训练好的&#xff0c;你肯定很好奇&#xff0c;我也想训练一个自己的专属模型&#xff08;也叫炼丹&#xff5e;_&#xff5e;&#xff09;。 甚至可以训练一个专属家庭版的模型&#xff08;fami…

JDK JRE JVM之间的关系

文章目录 1.从定义的角度解释JDK、JRE、JVM2、详细介绍JDK3、详细介绍JRE4、详细介绍JVM1、JVM内部区域划分 5、如何运行一个java程序&#xff1f; 本篇文章仅仅是个人片面观点&#xff0c;可能有错误或者表述不清的地方 1.从定义的角度解释JDK、JRE、JVM JDK&#xff1a;Java…

ArcGIS面要素最小外接矩形、外接圆的绘制方法

本文介绍在ArcMap软件中&#xff0c;基于一个面图层&#xff0c;绘制其中面要素的最小外接矩形、最小外接圆等的方法。 首先&#xff0c;我们来看一下本文需要实现的需求。现有一个面要素图层&#xff0c;其中包含多个面要素&#xff0c;如下图所示。我们希望绘制这个面要素图层…

【Redis】Redis十大数据类型—列表List

介绍 List列表是简单的字符串列表&#xff0c;按照插入顺序排序&#xff0c;可以从头部或尾部向List列表添加元素。 列表的最大长度是2^32-1&#xff0c;也就是每个列表支持超过40亿个元素。 实现 底层数据结构是由双向链表或压缩列表实现。 如果列表的元素个数小于 512 个…

Python获取某乎问答区计算机专业学生应聘保洁这一内容,看看为啥会有此事发生

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 不知道现在还有多少人玩知某乎&#xff0c; 今天刷话题的时候看到这么一个问答&#xff0c; 这么有趣的话题&#xff0c;咱们就对其问答内容进行采集一下&#xff1f;&#xff1f; 效果展示 可以看到&#xff0c;数据…

Linux 远程访问控制 SSH SCP SFTP TCP-Wrappers

SSH&#xff08;secure shell&#xff09;协议 一种安全通道协议&#xff0c;主要用来实现字符界面的远程登录、远程复制等功能。 协议对通信双方的数据传输进行了加密处理&#xff0c;其中包括用户登录时输入的用户口令 SSH客户端<-----------------网络------------------…

android 布局优化

1.绘制和布局加载原理 本文仅供个人学习记录&#xff0c;详细介绍可查看下面链接 Android布局优化&#xff0c;多套方案全面解析 布局优化的原因&#xff1a;布局嵌套过深&#xff0c;或者其他原因导致布局渲染性能不佳&#xff0c;可能会导致应用卡顿。 android绘制原理&am…