Linux基础——Makefile编写优化(二)

简介: 本文主要是为了讲述如何能够编写更专业的Makefile,而不是仅仅通过一项简单的依赖规则生成几个固定目标。(重点参考李云老师编写的《专业嵌入式软件开发——全名走向高质量高效编程》一书,该书是我在图书馆偶然看到,发现原来长期一直使用的Makefile中居然还有这么多需要注意的细节,所以特地整理记录下来。)

addprefix函数

addprefix函数被用于给名字列表_names中的每一个名字增加前缀_prefix,并将增加了前缀的名字列表返回。其形式:


$(addprefix _prefix, _names)


如下示例使用:


.PHONY : all
without_dir = foo.c bar.c main.o
with_dir := $(addprefix objs/, $(without_dir))
all:
  @echo $(with_dir)

执行结果如下:

20200119142526364.png

addsuffix函数

addsuffix函数被用于给名字列表_names中的每个名字增加后缀_suffix,并将增加了后缀_suffix的名字列表返回。其形式如下:

$(addsuffix _suffix, _names)

使用示例:

.PHONY : all
without_suffix = foo bar main
with_suffix := $(addsuffix .c, $(without_suffix))
all:
  @echo $(with_suffix)

执行如下:

20200119142915210.png

eval函数

eval函数的存在使得Makefile具有动态语言的特征。eval函数使得make将再一次解析_text语句,eval函数返回值为空字符串,形式如下:


$(eval _text)


如下示例说明了它的用法:


.PHONY : all
sources = foo.c bar.c baz.s ugh.h
$(eval sources := $(filter %.c %.s, $(sources)))
all:
  @echo $(sources)

执行结果如下:

20200119143431172.png

filter函数

filter函数被用于从一个 名字列表 _text中根据模式_pattern得到满足需要的名字列表并返回。其形式:

$(filter _pattern, _text)

如下示例使用方式:


.PHONY : all
sources = foo.c bar.c baz.s ugh.h
sources := $(filter %.c %.s, $(sources))
all:
  @echo $(sources)

执行结果如下:

20200119143838109.png

filter-out函数

filter-out函数被用于从名字列表_text中根据模式_pattern滤除一部分名字,并将滤除后的列表返回。其形式如下:


$(filter-out _pattern, _text)


如下示例说明了它的用法:


.PHONY : all
objects = main1.o foo.o main2.o bar.o
result = $(filter-out main%.o, $(objects))
all:
  @echo $(result)


执行结果如下:

20200119144211513.png

notdir函数

notdir函数被用来从路径_names中抽取文件名,并将文件名返回。其形式如下:


$(notdir _names)

如下示例说明了它的用法:

.PHONY : all
file_name := $(notdir code/foo/src/foo.c code/bar/src/bar.c)
all:
  @echo $(file_name)

执行结果:

20200119144546832.png

patsubst函数

patsubst函数被用来将名字列表_text中符合_pattern模式的名字替换为_replacement,并将替换后的名字列表返回。其使用形式如下:


$(patsubst _pattern, _replacement, _text)


如下示例说明了它的使用方法,这里采用patsubst函数进行字符串替换,将所有.c结尾的名字替换为.o结尾的文件名。


.PHONY : all
mixed = foo.c bar.c main.o
objects := $(patsubst %.c, %.o, $(mixed)
all:
  @echo $(objects)


执行结果如下:

20200119145037315.png

#### realpath函数

realpath函数被用于获取_names所对应的真实路径名。其形式如下:


$(realpath _names)

如下示例说明了它的用法:


.PHONY : all
ROOT := $(realpath ./..)
all:
  @echo $(ROOT)


执行结果如下:

20200119145339812.png


strip函数

如果希望清除名字列表中的多余空格,strip函数就是最好的选择。strip函数将_string中的多余空格去除后返回。形式如下:


$(strip _string)

示例使用如下:

.PHONY : all
original = foo.c bar.c
stripped := $(strip $(original))
all:
  @echo "original = $(original)"
  @echo "stripped = $(stripped)"


执行后结果如下:

20200119145707234.png


wildcard函数

wildcard是通配符函数,通过它可以得到当前工作目录中满足_pattern模式的文件或目录名列表。形式如下:


$(wildcard _pattern)


如下示例说明了Makefile通过使用wildcard函数得到所以C源文件的名字列表。


.PHONY : all
SRCS = $(wildcard *.c)
all:
  @echo $(SRCS)


执行结果如下:


2020011915004416.png

提高编译环境的实用性

之前的Makefile示例也只是为了介绍Makefile中可以用到的各种规则、变量、函数的使用方法。接下来,我们来构建一个略微复杂的虚拟项目——complicated项目,该项目中用到源代码如下:


  • foo.h
#ifndef _FOO_H
#define _FOO_H
void foo();
#endif


  • foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
  printf("this is foo()!\n");
}


  • main.c
#include "foo.h"
int main()
{
  foo();
  return 0;
}


让编译环境更加有序

大多的软件项目都会通过合理地设计目录结构来提高它的可维护性。在编译一个项目时会产生大量的中间文件,如果中间文件与项目的源程序文件直接混放在一起,就会显得混乱不堪而不利于维护。

这里,我们通过使用目录来使得编译环境更加有序。目录的引入并不是一步到位的,我们还将在后面的项目再进一步探讨这个问题。首先,在编写Makefile之前,需要先了解对目录结构的需求,包括:


  • 将所有的目录文件放入objs子目录中。
  • 将最终生成的可执行程序放入exes子目录中。

目录的自动创建与删除

在编译目录职期间按,需要将存放生成文件的目录准备好。目录可以在项目编译之前通过手工去创建,但是我们更喜欢在编译过程中自动生成的方式。

要实现在编译过程中自动创建目录,需要记住一点:目录也是一个目标。具有自动创建目录的Makefile和其运行结果如图所示:


.PHONY: all
MKDIR = mkdir
DIRS = objs exes
all: $(DIRS)
$(DIRS):
  $(MKDIR) $@

20200119152419365.png

接下来增加一个clean目标用于清除编译目标目录,如下:


.PHONY: all
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
DIRS = objs exes
all: $(DIRS)
$(DIRS):
  $(MKDIR) $@
clean:
  $(RM) $(RMFLAGS) $(DIRS)

20200119155203817.png

通过目录管理文件

为了将项目编译时所创建的文件分别放入objs和exes目录中,需要用到Makefile中的一个函数——addprefix。如下修改:


.PHONY: all
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
all: $(DIRS) $(EXE)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(OBJS)
  $(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c
  $(CC) -o $@ -c $^
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)

20200119155923378.png

上面的修改主要有三种变化:


  • 通过运用addprefix函数,为每一个生成的目标文件加上“objs/”前缀,以便生成的文件放入objs目录中。
  • 在构建目标文件的规则中为目标名字机上“objs/”前缀,即增加“$(*DIR_OBJS)/”前缀。
  • 在clean规则的命令中增加对$(EXE)目标的删除。

提升依赖关系管理

现在假设对项目已经进行了一次成功的编译,这一点很重要,否则看不到现有的Makefile存在的问题。接着,将foo.h文件内容进行修改,但是不修改foo.c文件。理论上,头文件和源文件函数声明定义不同应该会编译出错。

20200119160548503.png

当修改后执行make后,结果居然是告诉我们没有什么事可做,此时如果执行make clean后重新make则发现会报错:

20200119160711472.png

那么是为什么make不能发现foo.h已经更改而进行重新编译呢。从Makefile文件中的内容能看出来它并不知道有foo.h文件的存在,而之所以编译会用到foo.h是因为foo.c和main.c中包含了头文件。如下:

20200119160917229.png

为了修复这一点,最简单直接的办法就是将foo.h文件通过依赖关系树纳入make的视野中,改动后的Makefile如下所示:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
all: $(DIRS) $(EXE)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(OBJS)
  $(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c foo.h
  $(CC) -o $@ -c $<
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)


执行结果如下:

20200119161409750.png

执行后正常,但是如果将每一个头文件都写入到Makefile的相应规则中,那必然很不妥当。


相关文章
|
7天前
|
缓存 监控 关系型数据库
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
12 0
|
2月前
|
存储 Linux
Linux基础项目开发1:量产工具——改进优化(八)
Linux基础项目开发1:量产工具——改进优化(八)
34 0
Linux基础项目开发1:量产工具——改进优化(八)
|
2月前
|
Linux 编译器 C语言
Linux应用开发基础知识——Makefile 的使用(二)
Linux应用开发基础知识——Makefile 的使用(二)
40 0
Linux应用开发基础知识——Makefile 的使用(二)
|
1月前
|
Linux 编译器 开发工具
Linux学习第二枪(yum,vim,g++/gcc,makefile的使用)
Linux学习第二枪(yum,vim,g++/gcc,makefile的使用)
|
1月前
|
IDE Linux 编译器
【Linux】项目自动化构建工具 —— make/Makefile
【Linux】项目自动化构建工具 —— make/Makefile
|
2月前
|
IDE Linux 开发工具
【Linux】Linux项目自动化构建工具-make/Makefile
【Linux】Linux项目自动化构建工具-make/Makefile
【Linux】Linux项目自动化构建工具-make/Makefile
|
1月前
|
IDE 小程序 Linux
【linux】Linux项目自动化构建工具-make/Makefile
【linux】Linux项目自动化构建工具-make/Makefile
27 0
|
2天前
|
Linux 开发工具 C语言
【linux基础(七)】Linux中的开发工具(下)--make/makefile和git
【linux基础(七)】Linux中的开发工具(下)--make/makefile和git
|
21天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
1月前
|
NoSQL Java Linux
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
62 0