Makefile学习总结

Makefile学习总结

目录

  • Makefile学习总结
    • 1. Makefile介绍
    • 2. Makefile规则
    • 3. Makefile文件里的赋值方法
    • 4. Makefile常用函数
      • 4.1 字符串替换和分析函数
      • 4.2 文件名函数
      • 4.3 其他函数
    • 5. Makefile使用示例
    • 6、多级目录通用Makefile Demo
      • 6.1 一般通用Makefile的设计思想
      • 6.2 Demo分析

参考教程:

韦东山老师教程

《跟我一起写Makefile》文档

Makefile官方文档
GCC参考文章

1. Makefile介绍

Makefile是一种用于自动化构建过程的脚本文件,广泛应用于软件开发中。它定义了如何从源代码构建目标文件(如可执行文件或库文件)的一系列规则和依赖关系。Makefile通常与make工具一起使用,make是一个命令行工具,它可以解析Makefile中的指令并执行相应的构建任务。

Makefile概念:

  • 目标(Target):Makefile 中的一个目标通常是一个文件,它是由一系列依赖文件通过一个或多个命令生成的。最终的目标文件通常是可执行文件或库文件。
  • 依赖(Dependencies):目标文件的生成需要依赖其他文件(如源代码文件或其他目标文件),这些文件被称为依赖文件。
  • 命令(Commands):用于生成目标文件的一系列 shell 命令。
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)

如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的2个条件:依赖文件比目标文件,或是目标文件还没生成

Makefile组成:

  • 目标:最终希望生成的文件。
  • 依赖文件:生成目标所需的文件。
  • 命令:用于生成目标的一系列 shell 命令。
  • 变量:用于简化 Makefile 编写和维护的变量。
  • 模式规则(Pattern Rules):自动处理常见文件扩展名转换的规则。
  • 隐含规则(Implicit Rules):Make 自带的一些预定义规则,用于简化 Makefile 编写。

2. Makefile规则

每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。这是容易出错的地方。

通常,如果一个依赖发生了变化,就需要规则调用命令以更新或创建目标。但是并非所有的目标都有依赖,例如,目标“clean”的作用是清除文件,它没有依赖。

规则一般是用于解释怎样和何时重建目标。make首先调用命令处理依赖,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作,即打印提示信息。

一个Makefile文件可以包含规则以外的其他文本,但一个简单的Makefile文件仅仅需要包含规则。虽然真正的规则比这里展示的例子复杂,但格式是完全一样的。

例如Makefile:

hello : hello.c
gcc -o hello hello.c

执行make命令时,仅当hello.c文件比hello文件新,才会执行命令gcc –o hello hello.c生成可执行文件hello;如果还没有hello文件,这个命令也会执行。

3. Makefile文件里的赋值方法

变量的定义语法形式如下:

immediate = deferred
immediate ?= deferred
immediate := immediate
immediate += deferred or immediate
define immediate
deferred
endef

在GNU make中对变量的赋值有两种方式:延时变量、立即变量。区别在于它们的定义方式和扩展时的方式不同,前者在这个变量使用时才扩展开,意即当真正使用时这个变量的值才确定;后者在定义时它的值就已经确定了。使用=?=定义或使用define指令定义的变量是延时变量;使用:=定义的变量是立即变量。需要注意的一点是,?=仅仅在变量还没有定义的情况下有效,即?=被用来定义第一次出现的延时变量。

对于附加操作符+=,右边变量如果在前面使用:=定义为立即变量则它也是立即变量,否则均为延时变量。

  • 简单赋值(=:右侧的值在赋值时立即展开。
  • 条件赋值(?=:只有在变量未定义时才赋值。
  • 双冒号赋值(:=:右侧的值在使用变量时展开。
  • 追加赋值(+=:右侧的值追加到已有值的末尾。

一个综合性的Makefile示例,展示了这四种赋值方法的使用:

CC = gcc
CFLAGS = -Wall
CFLAGS += -g
CFLAGS ::= $(patsubst %.c,%.o,$(wildcard *.c))
CFLAGS ?= -O2SOURCES = main.c foo.c bar.c
LDFLAGS ?= -lmall: programprogram: $(SOURCES:.c=.o)$(CC) $(CFLAGS) $(LDFLAGS) -o program $(SOURCES:.c=.o)%.o: %.c$(CC) $(CFLAGS) -c -o $@ $<clean:rm -f program *.o

输出:

  • CC 的值为 gcc
  • CFLAGS 的值为 -Wall -g+= 追加赋值后,:= 的赋值被覆盖)
  • SOURCES 的值为 main.c foo.c bar.c
  • LDFLAGS 的值为 -lm

4. Makefile常用函数

函数调用的格式如下:

$(function arguments)

这里function是函数名,arguments是该函数的参数。参数和函数名之间是用空格或Tab隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。内核的Makefile中用到大量的函数,现在介绍一些常用的。

4.1 字符串替换和分析函数

1、$(subst from,to,text)

在文本text中使用to替换每一处from
比如:

$(subst ee,EE,feet on the street)

结果为:

  • fEEt on the strEEt

2、$(patsubst pattern,replacement,text)

寻找text中符合格式pattern的字,用replacement替换它们。patternreplacement中可以使用通配符。

比如:

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

结果为:

  • x.c.o bar.o

3、$(strip string)

去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。

比如:

$(strip a  b c )

结果为:

  • a b c

4、$(findstring find,in)

在字符串in中搜寻find,如果找到,则返回值是find,否则返回值为空。

比如:

$(findstring a,a b c)
$(findstring a,b c)

结果为:

  • a和``(空字符串)

5、$(filter pattern…,text)

返回在text中由空格隔开且匹配格式pattern...的字,去除不符合格式pattern...的字。

比如:

$(filter %.c %.s,foo.c bar.c baz.s ugh.h)

结果为:

  • foo.c bar.c baz.s

6、 $(filter-out pattern…,text)

返回在text中由空格隔开且不匹配格式pattern...的字,去除符合格式pattern...的字。它是函数filter的反函数。

比如:

$(filter %.c %.s,foo.c bar.c baz.s ugh.h)

结果为:

  • ugh.h

7、$(sort list)

list中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。

比如:

$(sort foo bar lose)

结果为:

  • bar foo lose

4.2 文件名函数

1、$(dir names…)

抽取names...中每一个文件名的路径部分,文件名的路径部分包括从文件名的首字符到最后一个斜杠(含斜杠)之前的一切字符。

比如:

$(dir src/foo.c hacks)

结果为:

  • src/ ./

2、$(notdir names…)

抽取names...中每一个文件名中除路径部分外一切字符(真正的文件名)。

比如:

$(notdir src/foo.c hacks)

结果为:

  • foo.c hacks

3、$(suffix names…)

抽取names...中每一个文件名的后缀。

比如:

$(suffix src/foo.c src-1.0/bar.c hacks)

结果为:

  • .c .c

4、$(basename names…)

抽取names...中每一个文件名中除后缀外一切字符。

比如:

$(basename src/foo.c src-1.0/bar hacks)

结果为:

  • src/foo src-1.0/bar hacks

5、$(addsuffix suffix,names…)

参数names...是一系列的文件名,文件名之间用空格隔开;suffix是一个后缀名。将suffix(后缀)的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间用单个空格隔开。

比如:

$(addsuffix .c,foo bar)

结果为:

  • foo.c bar.c

6、$(addprefix prefix,names…)

参数names是一系列的文件名,文件名之间用空格隔开;prefix是一个前缀名。将preffix(前缀)的值附加在每一个独立文件名的前面,完成后将文件名串联起来,它们之间用单个空格隔开。

比如:

$(addprefix src/,foo bar)

结果为:

  • src/foo src/bar

7、$(wildcard pattern)

参数‘pattern’是一个文件名格式,包含有通配符(通配符和shell中的用法一样)。函数wildcard的结果是一列和格式匹配的且真实存在的文件的名称,文件名之间用一个空格隔开。

比如若当前目录下有文件 1.c、2.c、1.h、2.h,则:

c_src := $(wildcard *.c)

结果为:

  • 1.c 2.c

4.3 其他函数

1、$(foreach var,list,text)

前两个参数,varlist将首先扩展,注意最后一个参数text此时不扩展;接着,list扩展所得的每个字,都赋给var变量;然后text引用该变量进行扩展,因此text每次扩展都不相同。

函数的结果是由空格隔开的textlist中多次扩展后,得到的新list,就是说:text多次扩展的字串联起来,字与字之间由空格隔开,如此就产生了函数foreach的返回值。

下面是一个简单的例子,将变量‘files’的值设置为‘dirs’中的所有目录下的所有文件的列表:

dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))

这里text$(wildcard $(dir)/*),它的扩展过程如下:

  • 第一个赋给变量dir的值是a,扩展结果为$(wildcarda/*)
  • 第二个赋给变量dir的值是b,扩展结果为$(wildcardb/*)
  • 第三个赋给变量dir的值是c,扩展结果为$(wildcardc/*)
  • 如此继续扩展。

这个例子和下面的例有共同的结果:

files := $(wildcard a/* b/* c/* d/*)

2、$(if condition,then-part[,else-part])

首先把第一个参数condition的前导空格、结尾空格去掉,然后扩展。如果扩展为非空字符串,则条件condition;如果扩展为空字符串,则条件condition
如果条件condition,那么计算第二个参数then-part的值,并将该值作为整个函数if的值。
如果条件condition,并且第三个参数存在,则计算第三个参数else-part的值,并将该值作为整个函数if的值;如果第三个参数不存在,函数if将什么也不计算,返回空值。

**注意:**仅能计算then-partelse-part二者之一,不能同时计算。这样有可能产生副作用(例如函数shell的调用)。

3、$(origin variable)

变量variable是一个查询变量的名称,不是对该变量的引用。所以,不能采用$和圆括号的格式书写该变量,当然,如果需要使用非常量的文件名,可以在文件名中使用变量引用。

函数origin的结果是一个字符串,该字符串变量是这样定义的:

'undefined'				:如果变量‘variable’从没有定义;
'default'				:变量‘variable’是缺省定义;
'environment'			:变量‘variable’作为环境变量定义,选项‘-e’没有打开;
'environment override'	:变量‘variable’作为环境变量定义,选项‘-e’已打开;
'file'					:变量‘variable’在 Makefile 中定义;
'command line'			:变量‘variable’在命令行中定义;
'override'				:变量‘variable’在 Makefile 中用 override 指令定义;
'automatic' 			:变量‘variable’是自动变量

4、$(shell command arguments)

函数shell是make与外部环境的通讯工具。函数shell的执行结果和在控制台上执行command arguments的结果相似。不过如果command arguments的结果含有换行符(和回车符),则在函数shell的返回结果中将把它们处理为单个空格,若返回结果最后是换行符(和回车符)则被去掉。

比如当前目录下有文件1.c、2.c、1.h、2.h,则:

c_src := $(shell ls *.c)

结果为:

  • 1.c 2.c

5. Makefile使用示例

示例背景:假设当前目录下有main.c、sub.c、sub.h三个文件,main.c中调用了sub.h中声明的sub.c中的函数,main.c引用了sub.h头文件

在当前目录下编写Makefile


1、第1个Makefile,简单粗暴,效率低:

test : main.c sub.c sub.h
gcc -o test main.c sub.c

2、第2个Makefile,效率高,相似规则太多太啰嗦,不支持检测头文件:

test : main.o sub.o
gcc -o test main.o sub.omain.o : main.c
gcc -c -o main.o main.csub.o : sub.c
gcc -c -o sub.o sub.cclean:
rm *.o test -f

3、第3个 Makefile,效率高,精炼,不支持检测头文件:

test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o $@ $<clean:
rm *.o test -f

4、第4个Makefile,效率高,精炼,支持检测头文件(但是需要手工添加头文件规则):

test : main.o sub.o
gcc -o test main.o sub.o%.o : %.c
gcc -c -o $@ $<sub.o : sub.hclean:
rm *.o test -f

5、第5个Makefile,效率高,精炼,支持自动检测头文件:

objs := main.o sub.otest : $(objs)gcc -o test $^
# 
需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))# 把依赖文件包含进来
ifneq ($(dep_files),)include $(dep_files)
endif%.o : %.cgcc -Wp,-MD,.$@.d -c -o $@ $<clean:rm *.o test -fdistclean:rm $(dep_files) *.o test -f

6、最终版

01 src := $(shell ls *.c)
02 objs := $(patsubst %.c,%.o,$(src))
03
04 test: $(objs)
05 gcc -o $@ $^
06
07 %.o:%.c
08 gcc -c -o $@ $<
09
10 clean:
11 rm -f test *.o

上述Makefile中$@$^$<称为自动变量。$@表示规则的目标文件名;$^表示所有依赖的名字,名字之间用空格隔开;$<表示第一个依赖的文件名。%是通配符,它和一个字符串中任意个数的字符相匹配。

一行行地分析:

  • 第1行src变量的值为main.c sub.c

  • 第2行objs变量的值为main.o sub.o,是src变量经过patsubst函数处理后得到的。

  • 第4行实际上就是:

    • test : main.o sub.o
      
    • 目标test的依赖有二:main.o和sub.o。开始时这两个文件还没有生成,在执行生成test的命令之前先将main.o、sub.o作为目标查找到合适的规则,以生成main.o、sub.o。

  • 第7、8行就是用来生成main.o、sub.o的规则:

    • 对于main.o这个规则就是:

    • main.o:main.cgcc -c -o main.o main.c
      
    • 对于sub.o这个规则就是:

    • sub.o:sub.cgcc -c -o sub.o sub.c
      
    • 这样,test的依赖main.o和sub.o就生成了。

  • 第5行的命令在生成main.o、sub.o后得以执行。

    • 在options目录下第一次执行make命令可以看到如下信息:

    • gcc -c -o main.o main.c
      gcc -c -o sub.o sub.c
      gcc -o test main.o sub.o
      
    • 然后修改sub.c文件,再次执行make命令,可以看到如下信息:

    • gcc -c -o sub.o sub.c
      gcc -o test main.o sub.o
      
    • 可见,只编译了更新过的sub.c文件,对main.c文件不用再次编译,节省了编译的时间。

6、多级目录通用Makefile Demo

参考源文件:《多级目录Makefile示例》

目录环境说明:

  • 当前目录下有main.c、sub.c、Makefile(根目录)、Makefile.build、目录a、目录include
    • 目录a下有sub2.c、sub3.c、Makefile(子目录)
    • 目录include下有sub.h、sub2.h、sub3.h

在这里插入图片描述

6.1 一般通用Makefile的设计思想

  • 在Makefile文件中确定要编译的文件、目录,比如:
obj-y += main.o
obj-y += a/

“Makefile”文件总是被“Makefile.build”包含的。

  • 在Makefile.build中设置编译规则,有3条编译规则:

    • 怎么编译子目录?进入子目录编译:
    $(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.build
    
    • 怎么编译当前目录中的文件?
    %.o : %.c$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
    
    • 当前目录下的.o和子目录下的built-in.o要打包起来:
    built-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^
    
  • 顶层Makefile中把顶层目录的built-in.o链接成APP:

$(TARGET) : built-in.o$(CC) $(LDFLAGS) -o $(TARGET) built-in.o

6.2 Demo分析

1、子目录Makefile

# 定义DEBUG宏,在当前子目录下所有文件中都能够使用该宏
EXTRA_CFLAGS := -D DEBUG# 定义DEBUG_SUB3宏,指定仅sub3.c才能使用该宏
CFLAGS_sub3.o := -D DEBUG_SUB3obj-y += sub2.o 
obj-y += sub3.o #EXTRA_CFLAGS  := 
#CFLAGS_file.o := 
#
#obj-y += file.o
#obj-y += subdir/
#   
#   "obj-y += file.o"  表示把当前目录下的file.c编进程序里,
#   "obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
#   "EXTRA_CFLAGS",    它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
#   "CFLAGS_xxx.o",    它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置# 注意: 
# 1. "subdir/"中的斜杠"/"不可省略
# 2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
# 3. CFLAGS  EXTRA_CFLAGS  CFLAGS_xxx.o 三者组成xxx.c的编译选项

2、顶层目录Makefile

CROSS_COMPILE = 
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nmSTRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/includeLDFLAGS := export CFLAGS LDFLAGSTOPDIR := $(shell pwd)
export TOPDIRTARGET := testobj-y += main.o
obj-y += sub.o
obj-y += a/all : start_recursive_build $(TARGET)@echo $(TARGET) has been built!start_recursive_build:make -C ./ -f $(TOPDIR)/Makefile.build$(TARGET) : start_recursive_build$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:rm -f $(shell find -name "*.o")rm -f $(TARGET)distclean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET)# 它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
# 主要是定义工具链前缀CROSS_COMPILE,
# 定义编译参数CFLAGS,
# 定义链接参数LDFLAGS,
# 这些参数就是文件中用export导出的各变量。

3、顶层目录Makefile.build

# 这里定义了一个伪目标__build,并且将其添加到了PHONY变量中。PHONY变量用来标记那些不是文件名的目标,而是用来触发一系列动作的伪目标。# 伪目标:为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
# 例如:.PHONY : clean
# 只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
# .PHONY: clean
# clean:
# rm *.o temp
PHONY := __build
__build:# 初始化了一些变量,其中obj-y和subdir-y分别用于收集对象文件和子目录。
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=# 包含Makefile,里面有:
# obj-y += main.o
# obj-y += sub.o
# obj-y += a/
include Makefile# 这部分代码从obj-y中提取出所有以/结尾的条目(即子目录),并去掉最后的/字符,将结果存储到subdir-y变量中。
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)# 对于每个子目录,生成一个built-in.o文件的列表。
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)# 这部分代码提取除了子目录之外的对象文件,并生成对应的依赖文件。dep_files变量存储了所有存在的依赖文件。
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))# 如果存在依赖文件,则将它们包含进来,以便Make能够根据这些文件自动处理依赖关系。
ifneq ($(dep_files),)include $(dep_files)
endif# 将子目录声明为伪目标,目的是避免错误,如果不将子目录名称声明为伪目标,当用户尝试执行像make a这样的命令时,Make会尝试去找一个名为a的文件。如果没有找到这样的文件,Make可能会报告错误,即使实际上a是一个子目录。
PHONY += $(subdir-y)# 定义了__build目标,它依赖于所有子目录的目标以及built-in.o文件。
__build : $(subdir-y) built-in.o# 对于每个子目录,调用make命令进入该目录并使用相同的Makefile.build文件构建。
$(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.build# built-in.o文件由当前目录的对象文件和子目录中的built-in.o文件链接而成。
built-in.o : $(subdir-y) $(cur_objs)$(LD) -r -o $@ $(cur_objs) $(subdir_objs)# 每个.c文件编译成.o文件时,也会生成一个依赖文件.d,其中包含了该.o文件的依赖项。
dep_file = .$@.d# 描述了如何将一个.c源文件编译成一个.o目标文件。
# $(CC):gcc 顶层目录Makefile中定义
# $(CFLAGS):-Wall -O2 -g -I $(shell pwd)/include 顶层目录Makefile中定义
# $(EXTRA_CFLAGS):-D DEBUG 子目录Makefile中定义,其中D是define的意思
# $(CFLAGS_$@):这是一个条件编译标志,允许为特定的目标文件指定特定的编译选项。这里的$@是目标文件名(例如foo.o),因此CFLAGS_foo.o可以定义特定于foo.o的编译选项。
# -Wp,-MD,$(dep_file):这是一个预处理器标志,它告诉编译器生成依赖文件。-MD选项要求编译器生成依赖信息,并将其写入到标准错误输出。-Wp选项允许向预处理器传递选项,这里将-MD选项连同依赖文件的名称一起传递给预处理器。
# -c:告诉编译器仅编译并汇编源文件,但不进行链接。
# -o $@:指定输出文件的名称。在这里,‘$@代表目标文件(例如foo.o`),因此编译后的目标文件将保存在这个文件中。
# $<:依赖文件(即源文件),在这里是指.c文件(例如foo.c)。
%.o : %.c$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<# 更新.PHONY目标,确保__build等伪目标被正确识别。
.PHONY : $(PHONY)

4、main.c

#include <stdio.h>extern void sub_fun(void);
extern void sub2_fun(void);
void sub3_fun(void);int main(int argc, char* argv[])
{printf("Main fun!\n");sub_fun();sub2_fun();sub3_fun();return 0;
}

5、sub.c

#include <stdio.h>
#include "sub.h"void sub_fun(void)
{printf("Sub fun,  A = %d!\n", A);   
}

6、sub.h

#define A  1
void sub_fun(void); 

7、sub2.c

#include <stdio.h>
#include <sub2.h>void sub2_fun(void)
{printf("Sub2 fun, B = %d!\n", B);   
#ifdef DEBUG //此宏定义由子目录Makefile中“EXTRA_CFLAGS := -D DEBUG”定义并传递到sub2.cprintf("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
}

8、sub2.h

#define B  2
void sub2_fun(void); 

9、sub3.c

#include <stdio.h>
#include <sub3.h>void sub3_fun(void)
{printf("Sub3 fun, C = %d!\n", C);#ifdef DEBUG //此宏定义由子目录Makefile中“EXTRA_CFLAGS := -D DEBUG”定义并传递到sub3.cprintf("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif#ifdef DEBUG_SUB3 //次宏定义由子目录Makefile中“CFLAGS_sub3.o := -D DEBUG_SUB3”指定定义到sub3.c中printf("It is only debug info for sub3.\n");
#endif}

10、sub3.h

#define C  3
void sub3_fun(void); 

在这里插入图片描述

在这里插入图片描述

make步骤分析:

  • 对每层目录都使用Makefile.build规则编译
  • 将子目录(a)下的所有.c文件(sub2.c、sub3.c)编译生成对应.o(sub2.o、sub3.o)文件,并将所有的.o(sub2.o、sub3.o)文件链接成built-in.o
  • 回到顶层目录,将顶层目录中的所有.c(main.c、sub.c)文件编译生成对应.o(main.o、sub.o)文件,将顶层目录中所有的.o(main.o、sub.o)文件和所有子目录的built-in.o文件链接生成最终的built-in.o文件
  • 最后使用最终的built-in.o文件生成可执行程序

针对有多个子目录或者多级子目录进行Makefile编译时,设计思想不改变,在此基础上进行扩展即可。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

可筛选的课程表设计excel表格@在线写作共享表格课程表设计模板参考

文章目录 abstract表格任务1. 时间段与课次安排2. 课程种类多样3. 教师与教室安排4. 课程颜色编码5. 课表标注 参考方案:样式预览全表添加不影响筛选列的跨列显示内容方案1方案2(pass) 针对指定老师筛选并生成课表&#x1f47a;在线表格链接(wps)要点表格说明&#x1f47a;列交…

Pow(x, n)

优质博文&#xff1a;IT-BLOG-CN 题目 实现pow(x, n) &#xff0c;即计算x的整数n次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例 2&#xff1a; 输入&#xff1a;x 2.100…

【spring】IDEA 新建一个spring boot 项目

参考新建项目-sprintboot 选择版本、依赖,我选了一堆 maven会重新下载一次么?

系统工程建模MBSE

################################# ############# 片段一 ############## ################################# 下图采用“V”模式显示了集成的基于模型的系统/嵌入式软件开发流程Harmony。左侧描述了自顶向下的设计流程,而右侧显示了自底而上的从单元测试到最终系统验收测试…

vue3 项目中使用git

一.vue项目创建 二.创建本地仓库并和远程仓库进行绑定 在vue3-project-git 项目文件夹下 初始化一个新的Git仓库&#xff0c;可以看到初始化成功之后就会出现一个.git文件&#xff0c;该文件包含所有必要的 Git 配置和版本控制信息。 创建远程仓库: 打开gitee ,点击右上角 ‘…

低代码用户中心:构建高效平台的新时代

一、低代码开发平台概述 低代码开发平台是一种通过图形化界面和预构建组件来简化应用开发的工具。开发者可以通过拖放组件和配置参数的方式&#xff0c;快速创建和修改应用程序&#xff0c;显著降低了编写代码的复杂度和时间成本。这种平台非常适合用来快速构建和部署企业内部…

Sapiens:人类视觉模型的基础

文章目录 摘要1、引言2、相关工作3、方法3.1、Humans-300M 数据集3.2、预训练3.3、二维姿态估计3.4、身体部位分割3.5、深度估计3.6、表面法线估计 4、实验4.1、实现细节4.2、二维姿态估计4.3、身体部位分割4.4、深度估计4.5、表面法线估计4.6、讨论 5、结论 摘要 我们介绍了 …

无线麦克风什么品牌好?麦克风领夹式的哪个牌子最好?麦克风推荐

近年来&#xff0c;无线领夹麦克风成为了网络主播、在线教育老师的新宠。它小巧便携&#xff0c;能够提供清晰的语音录制&#xff0c;完美匹配快节奏的工作与学习需求。但市场上的产品质量参差不齐&#xff0c;一些低价产品不仅音质差&#xff0c;甚至存在电池寿命短、兼容性差…

程序员的数字化工具有哪些?你用了多少?是否吓到你?

一、程序员常用的数字化工具有哪些&#xff1f; 程序员在日常工作中的数字化工具非常多样&#xff0c;涵盖了编码、测试、部署、协作等多个方面。以下是一些常见的工具&#xff1a; 集成开发环境&#xff08;IDE&#xff09;&#xff1a; IntelliJ IDEAEclipseVisual Studio Co…

(9月10日)最新植物大战僵尸杂交版【v2.4.0版本已更新】

植物大战僵尸杂交版下载链接【v2.4.0版本已更新】 新增了多种植物和僵尸&#xff0c;例如“海豌豆”、“豌豆海草”、“海洋星”等&#xff0c;以及新的僵尸类型&#xff0c;如“僵尸坚果巨人”和“僵尸豌豆小鬼”。 引入了新的游戏模式&#xff0c;例如“超级杂交地图”和“乒…

SQL进阶技巧:如何利用SQL解决趣味赛马问题?| 非等值关联匹配问题

目录 0 问题描述 1 数据准备 2 问题分析 方法一:先分后合思想 方法2:非等值关联匹配 3 小结 0 问题描述 有一张赛马记录表,如下所示: create table RacingResults ( trace_id char(3) not null,race_date date not null, race_nbr int not null,win_name char(30) n…

1万3医学考研题库医学题库ACCESS\EXCEL数据库

今天这个题库按知识点分章节模块智能练习&#xff0c;覆盖书本上所有知识点以及考点&#xff0c;在真#题的解析里边也有详细的展示&#xff1b;另外&#xff0c;这份数据库与《4820道西#医综合真题西#医真#题ACCESS数据库》、《4170条中#医综合真#题中医真#题ACCESS\EXCEL数据库…

去除恢复出厂设置中UI文字显示

文章目录 需求场景 一、代码跟踪与分析在线文字搜索RK平台本地源码搜索实际测试验证代码推理 二、实现方案三、延伸知识四、知识总结 需求 需求&#xff1a;去除恢复出厂设置中UI文字显示 场景 Android 相关产品各种方向旋转、强制横竖屏等需求&#xff0c;导致在恢复出厂设…

Bootstrap简介

Bootstrap 一.Bootstrap简介 什么是Bootstrap? Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架。Bootstrap 是基于 HTML、CSS、JAVASCRIPT 的。 为什么使用Bootstrap? 快速开发&#xff1a;Bootstrap 提供了一套预设的CSS样式和JavaScript组件&#xff0c;如…

JVM系列(七) -对象的内存分配流程

一、摘要 在之前的文章中,我们介绍了类加载的过程、JVM 内存布局和对象的创建过程相关的知识。 本篇综合之前的知识,重点介绍一下对象的内存分配流程。 二、对象的内存分配原则 在之前的 JVM 内存结构布局的文章中,我们介绍到了 Java 堆的内存布局,由 年轻代 (Young Ge…

LLM时代的transformer参数量、计算量、激活值的分析

导读&#xff1a;本文可以看作是对分析transformer模型的参数量、计算量、中间激活、KV cache的详细说明 定性分析 GPU上都存了哪些东西 首先我们来从全局整体的角度看一看&#xff0c;在训练阶段GPU显存上都有哪些内容&#xff1a; Model States&#xff1a;模型训练过程中…

标签的ref属性

标签的ref属性 当我们想要获取一个标签对应的 DOM 元素的时候在 JavaScript 中&#xff0c;我们通过 document.getElementById() 来借助类选择器的写法获取&#xff0c;但是在 Vue 中&#xff0c;我们的 DOM 元素是挂载在同一个网页上的&#xff0c;这些名称难免会重复&#x…

变压器制造5G智能工厂工业物联数字孪生平台,推进制造业数字化转型

变压器制造5G智能工厂工业物联数字孪生平台&#xff0c;推进制造业数字化转型。作为传统制造业的重要组成部分&#xff0c;变压器制造行业也不例外地踏上了数字化转型的快车道。而变压器制造5G智能工厂物联数字孪生平台的出现&#xff0c;更是为这一进程注入了强大的动力&#…

docker基本介绍

什么是docker docker是一个开源的容器平台&#xff0c;用于开发、交付和部署 运行应用程序 简单来说 也就是docke他允许开发者将自己的操作环境以及依赖关系打包成一个容器&#xff0c;移动到其他机器上可以供其他人使用&#xff0c;还可以打包成镜像&#xff0c;上传到网络&…

基于yolov8的血细胞检测计数系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的血细胞检测与计数系统是一种利用深度学习技术&#xff0c;特别是YOLOv8目标检测算法&#xff0c;实现高效、准确血细胞识别的系统。该系统能够自动识别并计数图像或视频中的血细胞&#xff0c;包括红细胞、白细胞和血小板等&#xff0c;为医疗诊断提…