16、构建与GNU Make的常见问题及算术实现

构建与GNU Make的常见问题及算术实现

在软件开发过程中,构建系统的效率和功能对于项目的顺利推进至关重要。本文将探讨一些构建相关的常见问题,以及如何利用GNU Make实现算术功能,甚至构建一个简单的计算器。

处理器数量与构建加速

在小型构建任务中,处理器数量对构建速度的提升存在一定的规律。根据Amdahl定律,当处理器数量达到约8个时,最大加速比会趋于平稳。实际情况中,由于构建任务只有13个可能的作业,这个平稳点会受到进一步限制。

以下是不同处理器数量对应的最大加速比表格:
| 处理器数量 | 最大加速比 |
| — | — |
| 10 | 2.46x |
| 11 | 2.50x |
| 12 | 2.53x |

从构建结构来看,最多使用8个处理器。因为有5个作业(t1、t2、t4、t6和t7)可以无依赖地并行运行,另外还有3个小作业链(t3、t5和t8;t9和t10;t11和t12),每个链每次使用一个处理器。而构建任务t可以复用这8个处理器中的一个,因为此时它们都处于空闲状态。

在实际应用中,像C和C++这类有链接步骤的语言,Amdahl定律对构建时间有显著影响。通常,所有目标文件在链接步骤之前构建完成,然后需要运行一个单独(通常很大)的链接进程。这个链接进程通常无法并行化,成为构建并行化的限制因素。

使 $(wildcard) 函数递归

GNU Make内置的 $(wildcard) 函数不是递归的,它只能在单个目录中搜索文件。不过,可以在 $(wildcard) 中使用多个通配符模式来查找子目录中的文件,例如 $(wildcard */*.c) 可以找到当前目录所有子目录中的 .c 文件。但如果需要搜索任意目录树,就没有内置的方法了。

幸运的是,可以很容易地创建一个递归版本的 $(wildcard) 函数:

rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))

这个 rwildcard 函数接受两个参数:第一个是搜索起始目录(可以留空以从当前目录开始),第二个是每个目录中要查找文件的通配符模式。

以下是使用示例:
- 查找当前目录(包括子目录)中的所有 .c 文件:

$(call rwildcard,,*.c)
  • 查找 /tmp 目录中的所有 .c 文件:
$(call rwildcard,/tmp/,*.c)

rwildcard 函数还支持多个模式,例如:

$(call rwildcard,/src/,*.c *.h)

这将查找 /src/ 目录下的所有 .c .h 文件。

确定当前Makefile的位置

在实际开发中,有时需要知道当前正在解析的Makefile的名称和路径。虽然GNU Make没有内置的快速获取方法,但可以使用 MAKEFILE_LIST 变量来实现。

MAKEFILE_LIST 是当前已加载或包含的Makefile列表。每次加载或包含一个Makefile时,其路径和名称会追加到 MAKEFILE_LIST 中。该变量中的路径和名称相对于当前工作目录(GNU Make启动的目录或使用 -C --directory 选项指定的目录),可以通过 CURDIR 变量访问当前目录。

可以定义一个GNU Make函数 where-am-i 来返回当前Makefile:

where-am-i = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))

要查找当前Makefile的完整路径,需要在Makefile的顶部添加以下代码:

THIS_MAKEFILE := $(call where-am-i)

这行代码必须放在顶部,因为Makefile中的任何 include 语句都会改变 MAKEFILE_LIST 的值,所以要在这之前获取当前Makefile的位置。

以下是一个示例Makefile,使用 where-am-i 函数并包含其他Makefile:

where-am-i = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
include foo/makefile

foo/makefile 的内容如下:

THIS_MAKEFILE := $(call where-am-i)
$(warning $(THIS_MAKEFILE))
include foo/bar/makefile

foo/bar/makefile 的内容如下:

THIS_MAKEFILE := $(call where-am-i)
$(warning $(THIS_MAKEFILE))

将这三个Makefile放在 /tmp 目录(及其子目录)中并运行GNU Make,输出如下:

foo/makefile:2: /tmp/foo/makefile
foo/bar/makefile:2: /tmp/foo/bar/makefile
GNU Make中的算术实现

GNU Make本身没有内置的算术功能,但可以使用其内置的列表和字符串操作函数来创建整数的加、减、乘、除以及比较函数。以下是具体的实现步骤和代码示例。

数字表示

为了实现算术功能,首先需要一种数字表示方法。这里使用包含相应数量元素的列表来表示数字,例如用 x x x x x 表示数字5。

可以使用 $(words) 函数将这种内部形式(全是 x )转换为人类可读的形式:

five := x x x x x
all: ; @echo $(words $(five))

还可以创建一个用户定义的函数 decode 来进行转换:

decode = $(words $1)

使用示例:

five := x x x x x
all: ; @echo $(call decode,$(five))
加法和减法

有了数字表示方法后,就可以定义加法、递增(加1)和递减(减1)函数:

plus = $1 $2
increment = x $1
decrement = $(wordlist 2,$(words $1),$1)

plus 函数将两个参数组合成一个列表,通过拼接实现加法; increment 函数在参数前添加一个 x decrement 函数从索引2开始获取整个 x 字符串,从而去掉第一个 x

以下代码将输出11:

two := x x
three := x x x
four := x x x x
five := x x x x x
six := x x x x x x
all: ; @echo $(call decode,$(call plus,$(five),$(six)))

还可以创建一个简单的函数 double 来将参数翻倍:

double = $1 $1

在实现减法之前,先实现 max min 函数:

max = $(subst xx,x,$(join $1,$2))
min = $(subst xx,x,$(filter xx,$(join $1,$2)))

max 函数使用 $(join) $(subst) 函数, $(join) 将两个列表连接起来, $(subst) 将匹配的元素替换为指定值。例如,计算 $(call max,$(five),$(six)) 的过程如下:

$(call max,$(five),$(six))
-> $(call max,x x x x x,x x x x x x)
-> $(subst xx,x,$(join x x x x x,x x x x x x))
-> $(subst xx,x,xx xx xx xx xx x)
-> x x x x x x

min 函数使用类似的方法,但只保留 xx 元素:

$(call min,$(five),$(six))
-> $(call min,x x x x x,x x x x x x)
-> $(subst xx,x,$(filter xx,$(join x x x x x,x x x x x x)))
-> $(subst xx,x,$(filter xx,xx xx xx xx xx x))
-> $(subst xx,x,xx xx xx xx xx)
-> x x x x x

减法函数的实现如下:

subtract = $(if $(call gte,$1,$2),         \
       $(filter-out xx,$(join $1,$2)),     \
       $(warning Subtraction underflow))

这里使用 gte 函数(大于或等于)来检查第一个参数是否大于或等于第二个参数,以避免减法下溢。 gte 函数由 gt (大于)和 eq (等于)函数实现:

gt = $(filter-out $(words $2),$(words $(call max,$1,$2)))
eq = $(filter $(words $1),$(words $2))
gte = $(call gt,$1,$2)$(call eq,$1,$2)

以下是 eq 函数的示例:

$(call eq,$(five),$(five)) 
-> $(call eq,x x x x x,x x x x x)
-> $(filter $(words x x x x x),$(words x x x x x))
-> $(filter 5,5)
-> 5

当两个参数相等时, eq 函数返回一个非空字符串,表示为真。

还可以定义其他比较运算符,如 ne (不等于)、 lt (小于)和 lte (小于或等于):

lt = $(filter-out $(words $1),$(words $(call max,$1,$2)))
ne = $(filter-out $(words $1),$(words $2))
lte = $(call lt,$1,$2)$(call eq,$1,$2)
乘法和除法

乘法函数使用 $(foreach) 函数实现:

multiply = $(foreach a,$1,$2)

例如:

$(call multiply,$(two),$(three))
-> $(call multiply,x x,x x x)
-> $(foreach a,x x,x x x)
-> x x x x x x

除法函数是最复杂的,需要递归实现:

divide = $(if $(call gte,$1,$2),             \
    x $(call divide,$(call subtract,$1,$2),$2),)

如果第一个参数小于第二个参数,除法返回0。例如:

$(call divide,$(three),$(two))
-> $(call divide,x x x,x x)
-> $(if $(call gte,x x x,x x),
   x $(call divide,$(call subtract,x x x,x x),x x),)
-> x $(call divide,$(call subtract,x x x,x x),x x)
-> x $(call divide,x,x x)
-> x $(if $(call gte,x,x x),
   x $(call divide,$(call subtract,x,x x),x x),)
-> x

对于除以2的特殊情况,可以定义 halve 函数来避免递归:

halve = $(subst xx,x,      \
   $(filter-out xy x y,    \
     $(join $1,$(foreach a,$1,y x))))

最后,需要一个 encode 函数将用户输入的数字转换为 x 字符串:

16 := x x x x x x x x x x x x x x x x
input_int := $(foreach a,$(16),      \
       $(foreach b,$(16),            \
        $(foreach c,$(16),$(16)))))
encode = $(wordlist 1,$1,$(input_int))

这里目前只能输入最大为65536的数字,可以通过改变 input_int x 的数量来扩展。

使用算术库构建计算器

为了展示这个算术库的功能,可以实现一个完全用GNU Make函数编写的逆波兰表达式计算器。

stack := 
push = $(eval stack := $$1 $(stack))
pop = $(word 1,$(stack))$(eval stack := $(wordlist 2,$(words $(stack)),$(stack)))
pope = $(call encode,$(call pop))
pushd = $(call push,$(call decode,$1))
comma := ,
calculate = $(foreach t,$(subst $(comma), ,$1),$(call handle,$t))$(stack)
seq = $(filter $1,$2)
handle = $(call pushd,                            \
    $(if $(call seq,+,$1),                        \
      $(call plus,$(call pope),$(call pope)),     \
      $(if $(call seq,-,$1),                      \
      $(call subtract,$(call pope),$(call pope)), \
        $(if $(call seq,*,$1),                    \
     $(call multiply,$(call pope),$(call pope)),  \
        $(if $(call seq,/,$1),                    \
       $(call divide,$(call pope),$(call pope)),  \
           $(call encode,$1))))))
.PHONY: calc
calc: ; @echo $(call calculate,$(calc))

运算符和数字通过 calc 变量传递给GNU Make,用逗号分隔。例如:

$ make calc="3,1,-,3,21,5,*,+,/"
54

以下是完整的注释版Makefile:

# input_int consists of 65536 x's built from the 16 x's in 16
16 := x x x x x x x x x x x x x x x x
input_int := $(foreach a,$(16),$(foreach b,$(16),$(foreach c,$(16),$(16)))))
# decode turns a number in x's representation into an integer for human
# consumption
decode = $(words $1)
# encode takes an integer and returns the appropriate x's
# representation of the number by chopping $1 x's from the start of
# input_int
encode = $(wordlist 1,$1,$(input_int))
# plus adds its two arguments, subtract subtracts its second argument
# from its first if and only if this would not result in a negative result
plus = $1 $2
subtract = $(if $(call gte,$1,$2),     \
       $(filter-out xx,$(join $1,$2)), \
       $(warning Subtraction underflow))
# multiply multiplies its two arguments and divide divides its first
# argument by its second
multiply = $(foreach a,$1,$2)
divide = $(if $(call gte,$1,$2),x $(call divide,$(call subtract,$1,$2),$2),)
# max returns the maximum of its arguments and min the minimum
max = $(subst xx,x,$(join $1,$2))
min = $(subst xx,x,$(filter xx,$(join $1,$2)))
# The following operators return a non-empty string if their result is true:
#
# gt    First argument is greater than second argument
# gte   First argument is greater than or equal to second argument
# lt    First argument is less than second argument 
# lte   First argument is less than or equal to second argument
# eq    First argument is numerically equal to the second argument
# ne    First argument is not numerically equal to the second argument
gt = $(filter-out $(words $2),$(words $(call max,$1,$2)))
lt = $(filter-out $(words $1),$(words $(call max,$1,$2)))
eq = $(filter $(words $1),$(words $2))
ne = $(filter-out $(words $1),$(words $2))
gte = $(call gt,$1,$2)$(call eq,$1,$2)
lte = $(call lt,$1,$2)$(call eq,$1,$2)
# increment adds 1 to its argument, decrement subtracts 1. Note that
# decrement does not range check and hence will not underflow, but
# will incorrectly say that 0 - 1 = 0
increment = $1 x
decrement = $(wordlist 2,$(words $1),$1)
# double doubles its argument, and halve halves it
double = $1 $1
halve = $(subst xx,x,$(filter-out xy x y,$(join $1,$(foreach a,$1,y x))))
# This code implements a Reverse Polish Notation calculator by
# transforming a comma-separated list of operators (+ - * /) and
# numbers stored in the calc variable into the appropriate calls to
# the arithmetic functions defined in this makefile.
# This is the current stack of numbers entered into the calculator. The push 
# function puts an item onto the top of the stack (the start of the list), and 
# pop removes the top item.
stack := 
push = $(eval stack := $$1 $(stack))
pop = $(word 1,$(stack))$(eval stack := $(wordlist 2,$(words $(stack)),$(stack)))
# pope pops a number off the stack and encodes it
# and pushd pushes a number onto the stack after decoding
pope = $(call encode,$(call pop))
pushd = $(call push,$(call decode,$1))
# calculate runs through the input numbers and operations and either
# pushes a number on the stack or pops two numbers off and does a
# calculation followed by pushing the result back. When calculate is
# finished, there will be one item on the stack, which is the result.
comma := ,
calculate=$(foreach t,$(subst $(comma), ,$1),$(call handle,$t))$(stack)
# seq is a string equality operator that returns true (a non-empty
# string) if the two strings are equal
seq = $(filter $1,$2)
# handle is used by calculate to handle a single token. If it's an
# operator, the appropriate operator function is called; if it's a
# number, it is pushed.
handle = $(call pushd,                            \
      $(if $(call seq,+,$1),                      \
        $(call plus,$(call pope),$(call pope)),   \
        $(if $(call seq,-,$1),                    \

通过以上内容,我们了解了构建过程中处理器数量与加速比的关系,掌握了 $(wildcard) 函数的递归使用和确定当前Makefile位置的方法,还实现了GNU Make中的算术功能和一个简单的计算器。这些技巧和方法在实际开发中可以帮助我们提高构建效率和解决一些复杂的问题。

构建与GNU Make的常见问题及算术实现

逆波兰表达式计算器的工作流程

为了更好地理解逆波兰表达式计算器的实现,下面详细介绍其工作流程。

graph TD
    A[输入表达式] --> B[分割令牌]
    B --> C{令牌类型}
    C -- 数字 --> D[压入栈]
    C -- 运算符 --> E[弹出操作数]
    E --> F[执行运算]
    F --> G[结果压入栈]
    D --> H{是否还有令牌}
    G --> H
    H -- 是 --> B
    H -- 否 --> I[输出栈顶结果]

具体步骤如下:
1. 输入表达式 :用户通过 calc 变量输入一个由逗号分隔的逆波兰表达式,例如 "3,1,-,3,21,5,*,+,/"
2. 分割令牌 :使用 $(subst) 函数将逗号替换为空格,然后使用 $(foreach) 函数遍历每个令牌。
3. 判断令牌类型 :使用 seq 函数判断令牌是数字还是运算符。
4. 数字处理 :如果是数字,使用 pushd 函数将其解码并压入栈中。
5. 运算符处理 :如果是运算符,使用 pop 函数弹出栈顶的两个操作数,执行相应的运算,然后将结果压入栈中。
6. 重复处理 :继续处理下一个令牌,直到所有令牌都处理完毕。
7. 输出结果 :最后,栈中只剩下一个元素,即为表达式的计算结果。

算术库的应用场景

虽然在GNU Make中实现算术功能不是其主要用途,但在某些特定场景下,这种实现可以发挥作用。

  • 条件判断 :在Makefile中,可以使用算术比较函数进行条件判断,例如根据某个变量的值决定是否执行特定的规则。
ifeq ($(call gte,$(var1),$(var2)),)
    # var1 < var2 的情况
else
    # var1 >= var2 的情况
endif
  • 动态生成规则 :可以根据算术运算的结果动态生成Makefile规则,例如根据文件数量生成不同的编译规则。
file_count := $(call decode,$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)))
if $(call gte,$(file_count),10)
    # 处理文件数量大于等于10的情况
else
    # 处理文件数量小于10的情况
endif
注意事项和扩展建议

在使用上述算术库和计算器时,需要注意以下几点:

  • 性能问题 :递归实现的除法函数 divide 在处理大数字时可能会导致性能问题,因为递归调用会增加计算时间和内存开销。可以考虑优化递归算法或使用迭代方法。
  • 数字范围限制 :目前 encode 函数只能处理最大为65536的数字,可以通过增加 input_int x 的数量来扩展数字范围,但这会增加内存使用。
  • 错误处理 :虽然在减法函数中添加了下溢警告,但在其他函数中可能还存在未处理的错误情况,例如除法时除数为0的情况。可以添加更多的错误处理代码来提高程序的健壮性。

扩展建议:
- 添加更多运算符 :可以扩展算术库,添加更多的运算符,如取模、幂运算等。
- 支持浮点数 :目前的实现只支持整数运算,可以考虑扩展为支持浮点数运算。

总结

通过本文的介绍,我们了解了构建过程中处理器数量与加速比的关系,掌握了 $(wildcard) 函数的递归使用和确定当前Makefile位置的方法,还实现了GNU Make中的算术功能和一个简单的逆波兰表达式计算器。这些技巧和方法在实际开发中可以帮助我们提高构建效率和解决一些复杂的问题。虽然在GNU Make中实现算术功能不是其主要用途,但在某些特定场景下,这种实现可以发挥作用。同时,我们也需要注意性能、数字范围和错误处理等问题,并可以根据实际需求进行扩展。

希望本文对你有所帮助,如果你在使用过程中遇到任何问题,欢迎留言讨论。

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合。
先展示下效果 https://pan.quark.cn/s/a4b39357ea24 本项目是本人参加BAT等其他公司电话、现场面试之后总结出来的针对Java面试的知识点或真题,每个点或题目都是在面试中被问过的。 除开知识点,一定要准备好以下套路: 个人介绍,需要准备一个1分钟的介绍,包括学习经历、工作经历、项目经历、个人优势、一句话总结。 一定要自己背得滚瓜烂熟,张口就来 抽象概念,当面试官问你是如何理解多线程的时候,你要知道从定义、来源、实现、问题、优化、应用方面系统性地回答 项目强化,至少知识点的比例是五五开,所以必须针对简历中的两个以上的项目,形成包括【架构和实现细节】,【正常流程和异常流程的处理】,【难点+坑+复盘优化】三位一体的组合拳 压力练习,面试的时候难免紧张,可能会严重影响发挥,通过平时多找机会参交流分享,或找人做压力面试来改善 表达练习,表达能力非常影响在面试中的表现,能否简练地将答案告诉面试官,可以通过给自己讲解的方式刻意练习 重点针对,面试官会针对简历提问,所以请针对简历上写的所有技术点进行重点准备 Java基础 JVM原理 集合 多线程 IO 问题排查 Web框架、数据库 Spring MySQL Redis 通用基础 操作系统 网络通信协议 排序算法 常用设计模式 从URL到看到网页的过程 分布式 CAP理论 锁 事务 消息队列 协调器 ID生成方式 一致性hash 限流 微服务 微服务介绍 服务发现 API网关 服务容错保护 服务配置中心 算法 数组-快速排序-第k大个数 数组-对撞指针-最大蓄水 数组-滑动窗口-最小连续子数组 数组-归并排序-合并有序数组 数组-顺时针打印矩形 数组-24点游戏 链表-链表反转-链表相加 链表-...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值