makefile详解

ops/2025/3/14 21:00:54/

1.背景介绍

linux中C语言可执行文件a.out的由来如下:

  • test.c经过预编译到test.i
  • test.i经过编译到test.s
  • test.s经过汇编到test.o
  • test.o经过链接到a.out

但是对于一个大型工程,每次生成新的可执行文件都要重新对每个文件(或者对修改了的文件)进行编译、链接等操作的工作量太大了。

因此提出就有了make/makefile,它是一个工程文件的编译链接规则,规则写好之后,只需要一个make就能生成可执行文件。

2.语法

2.1 语法格式

all: 目标文件1 目标文件2
目标文件1: 依赖文件1, 依赖文件2...
<tab>命令1
<tab>命令2...
目标文件2: 依赖文件1, 依赖文件2...
<tab>命令1
<tab>命令2...
  • 目标文件即要生成的文件,一半情况下makefile中的第一个目标文件为最终目标
  • 依赖文件:该文件由哪些文件生成
  • 命令:通过执行命令来由依赖文件生成目标文件。这里默认执行命令时会在终端输出命令的内容。如果在命令前加"@"符号,则终端隐藏命令,但是返回到标准输出的内容不会隐藏。
  • all:makefile文件默认只生成一个目标文件即完成编译。如果想要生成多个目标文件,则需要all并放在第一行列出所有的生成文件。

2.2 变量

2.2.1 自定义变量

变量定义:

  • 递归展开:VAR= var1 var2…多个值中间用空格隔开
  • 直接赋值:VAR:=var。

赋值操作:

  • “=”:使用"="进行赋值,变量的值是整个Makefile中最后被指定的值,例如:
VAR_A = A
VAR_B = $(VAR_A) B
VAR_A = AA

最后VIR_B的值为AA B而不是A B

  • “:=”:表示直接赋值,赋予当前位置的值
VAR_A := A
VAR_B := $(VAR_A) B
VAR_A := AA

最后VIR_B的值为A B

  • “?:”:表示如果该变量没有被赋值,则将后面的值赋给它
VAR ?= value

VAR的值为value

VAR := value1
VAR ?= value2

VAR的值为value1

  • “+=”:类似C语言,将右边的变量追加到左边的变量中。

“$“符号表示取变量的值,当变量名多于一个字符时,需要使用”()”

2.2.2 自动变量

  • “$^”: 表示所有的依赖文件,并以空格分开
  • “$@”: 表示生成的目标文件
  • “$<”: 代表第一个依赖文件
  • “$?”: 所有时间戳比目标文件晚的依赖文件,并以空格分开

2.2.3 预定义变量

  • CC:C编译器的名称,默认值为cc
  • RM:文件删除程序的名称,默认值为rm -f
  • CFLAGES:C编译器(gcc)的选项,默认无默认值,如-Wall、-g、-o
  • AR:库文件维护程序的名称,默认值为ar
  • CPP:C预编译器的名称,默认值为$(CC) -E
  • CPPFLAGS:C预编译器的选项,无默认值
  • CXXFLAGS:C++编译器(g++)的选项,无默认值

2.2.4 隐含变量

  • “%.o”: 任意的.o文件
  • “*.o”: 所有的.o文件

2.3 函数

2.3.1 名称处理函数

  1. wildcard
$(wildcard <pattern...>)

获取指定格式的文件列表
示例:

$(wildcard *.cpp test/*.cpp)

代表当前目录下所有的.cpp文件和test目录下所有的.cpp文件。

  1. dir
$(dir <names...>)

获取文件所在目录,本质上是获取最后一个"/“以前的内容,如果没有”/“,返回”./"
示例:

$(dir src/foo.c sum.txt)

返回"src/ ./"

  1. notdir
$(notdir <names...>)

获取一个文件路径的非目录不放呢,即获得文件名。本质上是获取最后一个反斜杠"/"之后的内容,如果没有反斜杠,则直接返回本身
示例:

$(notdir src/foo.c sum.txt)

返回"foo.c sum.txt"

  1. suffix
$(suffix <names...>)

获取文件的后缀,如果没有后缀,则返回空字符。本质是获取最后一个"."后面的内容
示例:

$(suffix src/foo.c test.c abc)

返回".c .c "

  1. basename
$(basename <names...>)

去除文件后缀
示例:

$(basename src/foo.c test.c abc)

返回"src/foo test abc"

2.3.2 字符串替换与分析函数

  1. subst
$(subst <src>,<dst>,<text>)

将<text>中的字符<src>替换为<dst>
示例:

$(subst aa,AA,aabbaa aAfd)

返回"AAbbAA aAfd"

  1. patsubst
$(patsubst <src_pattern>,<dst_pattern>,<text>)

使用目标字符格式替换源字符格式,常常搭配%使用,如果<src_pattern>和<dst_pattern>都包括%,那么此时%表示的字符内容是一样的。
示例:

$(patsubst %.cpp,%.o,a.cpp b.cpp)

返回"a.o b.o"

  1. $(C_SOURCES:.c=.o)
    将C_SOURCES变量中所有的.c替换成.o

  2. strip
    去掉字符串的开头和结尾的空格
    示例:

$(strip,  a.cpp b.cpp)
  1. findstring
    在某个字符串中查找指定字符串
    示例:
$(findstring a,a b c)
  1. filter
    保留指定格式的字符串
sources := a.c b.c c.c d.h
result := $(filter %.c %.s,$(sources))

返回"a.c b.c c.c"

  1. filter-out
    去除指定格式的字符串

  2. addprefix
    为字符串添加字符串

OBJ_FILES = main.o a.o b.o
PREFIXED_OBJ_FILES = $(addprefix obj/,$(OBJ_FILES))

返回"obj/main.o obj/a.o obj/b.o"

2.3.3 控制函数

  1. info
    向标准输出打印提示信息
$(info some debug info)

终端输出"some debug info"

  1. warning
    向标准输出打印,用于输出警告信息,make继续执行

  2. error
    向标准错误输出打印文本,make停止执行

2.3.4 其他函数

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

将<list>中的参数逐一取出放到<var>中,然后执行<text>中的表达式

  • 循环执行中:每执行一次循环都会返回一个字符串,foreach循环会将返回的字符串汇总,不同字符串通过空格分隔
  • 循环执行结束:当整个循环结束的时候,返回汇总的字符串

示例:

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

返回"a.o b.o c.o d.o"

  1. call
$(call <expression>,<parm1>,<parm2>,...)

调用自定义的函数或者表达式,也可以传参调用

  1. shell
    执行操作系统的shell命令,返回的是命令行命令执行的结果
    示例:
$(shell ps ajx | grep text)
  1. eval
$(eval <text>)

可以将<text>中的内容作为makefile的一部分,然后按照makefile的语法解析这些内容,无返回值

2.4 伪目标

伪目标的格式:

.PHONY:目标
目标:依赖
<tab>命令

伪目标与常规目标的区别(无.PHONY声明):

  • 常规目标会检查目标文件的修改时间与依赖文件的修改时间,如果目标文件的修改时间晚于依赖文件,则不用执行命令;反之再执行命令。
  • 伪目标无论何时都会执行命令

伪目标用来定义ALL的作用:

  • 避免与目标文件同名导致构建失败或者不小心touch了目标文件使得本应该重新编译链接的没有反应,提升鲁棒性
  • 大多数项目将ALL声明为伪目标,遵循社区约定

还用来定义无依赖对象的伪目标,例如:

.PHONY:clean
clean:rm *.o *.out

调用时要在命令行输入:“make clean”

3.编写方式

假定有如下a.c b.c c.c main.c 四个文件,将这四个文件编译链接形成一个目标文件output

3.1 Makefile+直接编译链接

output:a.c b.c c.c main.cgcc a.c b.c c.c main.c -o output

3.2 Makefile+编译+链接

output:a.o b.o c.o main.ogcc a.o b.o c.o main.o -o outputa.o:a.cgcc -c a.c a.o
b.o:b.cgcc -c b.c b.o
c.o:c.cgcc -c c.c c.o
main.o:main.cgcc -c main.c main.o

3.3 Makefile+变量

SRC = a.o b.o c.o main.o
TARGET = output
$(TARGET):$(SRC)$(CC) $^ -o $@a.o:a.c$(CC) $^ -o $@
b.o:b.c$(CC) $^ -o $@
c.o:c.c$(CC) $^ -o $@
main.o:main.c$(CC) $^ -o $@

3.4 Makefile+模式匹配

SRC = a.o b.o c.o main.o
TARGET = output
$(TARGET):$(SRC)$(CC) $^ -o $@%.o:%.c$(CC) $< -o $@

3.5 Makefile+函数

a.h和a.cpp和main.cpp文件生成一个项目的makefile文件如下:

CXX = g++
CXXFLAGS = -Wall -std=c++17SRCS = a.cpp main.cpp
OBJS = $(SRCS:.cpp=.o)
TARGET = outputall = $(TARGET)$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)%.o:%.cpp$(CXX) $(CXXFLAGS) -c $< -o $@clean:rm -f $(OBJS) $(TARGET).PHONY: all clean

问题:这里的.c文件的依赖文件.h为什么不写:
这里这么写是没有问题的,它会自动检测.c的.h依赖文件。但是会出现一个问题,如果是这里修改了.h文件,make是检测不出来依赖文件改变的,它只会看.c没变,然后就不会重新编译。因此每次修改之后都需要make clean && make重新生成。

所以可以改进一下,来可以显示生成依赖关系文件,然后-include到makefile文件中如下,这时make时候就会检测到.c对.h的依赖关系,会进行时间检测是否重新编译:

CXX = g++
CXXFLAGS = -Wall -std=c++17 -MMD -MP  # 启用依赖生成
SRCS = main.cpp utils.cpp
OBJS = $(SRCS:.cpp=.o)
DEPFILES = $(SRCS:.cpp=.d)
TARGET = appall: $(TARGET)$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) -o $@ $(OBJS)%.o: %.cpp$(CXX) $(CXXFLAGS) -c $< -o $@  #因为有MMD和MP参数所以会在执行这个的时候同时生成对应.d文件-include $(DEPFILES)  # 包含依赖关系clean:rm -f $(OBJS) $(TARGET) $(DEPFILES).PHONY: all clean

http://www.ppmy.cn/ops/165763.html

相关文章

云原生性能测试全解析:如何构建高效稳定的现代应用?

一、引言 随着云计算技术的快速发展&#xff0c;云原生&#xff08;Cloud Native&#xff09;架构成为现代应用开发的主流模式。云原生应用通常采用微服务架构、容器化部署&#xff0c;并利用 Kubernetes&#xff08;K8s&#xff09;等编排工具进行管理。然而&#xff0c;云原…

在线Doc/Docx转换为PDF格式 超快速转换的一款办公软件 文档快速转换 在线转换免费转换办公软件

小白工具https://www.xiaobaitool.net/files/word-pdf/提供了一项非常实用的在线服务——将Doc或Docx格式的文档快速转换为PDF格式。这项服务不仅操作简单&#xff0c;而且转换效率高&#xff0c;非常适合需要频繁处理文档转换的用户。 服务特点&#xff1a; 批量转换&#x…

微服务Sentinel组件:服务保护详解

目录 服务保护简介 服务保护方案 安装与介绍Sentinel Sentinel整合微服务 服务保护实现 请求限流 线程隔离 OpenFeign整合Sentinel 配置线程隔离 服务熔断 编写降级逻辑 实现服务熔断 服务保护总结 服务保护简介 微服务保护是为了保障系统整体的稳定性和可靠性&am…

【PyTorch教学】pytorch 基本语法

文章目录 PyTorch tensor cheatsheet PyTorch tensor cheatsheet from: https://github.com/hkproj/torch_notes/blob/main/TensorOperations.ipynb Tensor creation / initialization Convert between Numpy and Torch Tensor math Matrix multiplication Batch matrix multip…

蓝桥杯省赛真题C++B组2024-握手问题

一、题目 【问题描述】 小蓝组织了一场算法交流会议&#xff0c;总共有 50 人参加了本次会议。在会议上&#xff0c;大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手(且仅有一次)。但有 7 个人&#xff0c;这 7 人彼此之间没有进行握手(但这…

Uniapp当中的scroll-view滚动条不出现或者触底刷新事件不触发

一、未正确设置容器高度 问题描述 scroll-view 未设置明确高度或高度值无效&#xff0c;导致无法形成有效滚动区域。 解决方案 • 使用行内样式直接设置 height&#xff08;如 style"height: 500rpx;"&#xff09;&#xff0c;避免类名样式被覆盖。 • 动态计算高度…

计算机视觉算法实战——手势识别(主页有源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​ 1. 领域简介&#xff1a;手势识别的价值与挑战 手势识别是连接人类自然行为与数字世界的核心交互技术&#xff0c;在智能设备控制、…

代码随想录 DP day2

746. 使用最小花费爬楼梯 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;递推公式和爬楼梯类似&#xff0c;都是思考第i层由什么得来的呢&#xff1f;即i-1和i-2加上对应的cost。 class Solution { public:int minCostClimbingStairs(vector<int>& cost…