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的相应规则中,那必然很不妥当。


相关文章
|
10月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
2598 77
|
7月前
|
NoSQL Linux 开发工具
Linux环境基础开发工具的使用(yum、vim、gcc、g++、gdb、make/Makefile)
本文介绍了yum 包管理工具、Vim 编辑器、gcc/g++ 编译器、gdb 调试器、编译原理及 Makefile 的使用,同时还配备了如何使用,以及图解。旨在帮助读者更好地理解和应用这些工具与技术。
376 0
|
12月前
|
Rust Ubuntu Java
[Linux工具] Makefile
Makefile是Linux环境下用于自动化编译和链接程序的配置文件,常用于简化大型项目的编译流程。通过定义目标文件、依赖文件及生成命令,Makefile能高效管理编译任务。它不仅适用于C语言项目,还可扩展到其他编程语言和非编程任务中。
[Linux工具] Makefile
|
11月前
|
Unix Linux C语言
【Linux】 Linux makefile 教程
本文详细介绍了 Linux 环境下 Makefile 的基本结构、语法和使用方法,并通过一个简单的 C++ 项目示例演示了 Makefile 的实际应用。Makefile 是一个强大而灵活的工具,通过合理配置,可以极大地简化项目的编译和管理过程,提高开发效率。希望本文能帮助您更好地理解和应用 Makefile,在实际项目中高效管理代码的编译和构建。
751 20
|
11月前
|
Unix Linux C语言
【Linux】 Linux makefile 教程
本文详细介绍了 Linux 环境下 Makefile 的基本结构、语法和使用方法,并通过一个简单的 C++ 项目示例演示了 Makefile 的实际应用。Makefile 是一个强大而灵活的工具,通过合理配置,可以极大地简化项目的编译和管理过程,提高开发效率。希望本文能帮助您更好地理解和应用 Makefile,在实际项目中高效管理代码的编译和构建。
254 16
|
11月前
|
缓存 NoSQL Linux
Linux系统内存使用优化技巧
交换空间(Swap)的优化 禁用 Swap sudo swapoff -a 作用:这个命令会禁用系统中所有的 Swap 空间。swapoff 命令用于关闭 Swap 空间,-a 参数表示关闭 /etc/fstab 文件中配置的所有 Swap 空间。 使用场景:在高性能应用场景下,比如数据库服务器或高性能计算服务器,禁用 Swap 可以减少磁盘 I/O,提高系统性能。
436 3
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
542 24
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####