Makefile学习总结

news/2024/10/4 19:39:12/文章来源:https://blog.csdn.net/studyingdda/article/details/142097871

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.ldbm.cn/p/441548.html

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

相关文章

Linux基础知识之用户和组

Linux基础知识之用户和组 01 1.什么是用户 用户账户为不同人员和运行的程序之间提供安全边界 用户使用 username 进行标识&#xff0c;操作系统为其分配唯一标识号&#xff08;UID&#xff09;&#xff0c;用户可能含有密码 系统中的每个程序都以一个特定用户运行&#xff…

校园管理|基于springboot+vue的校园管理系统(源码+数据库+文档)

校园管理|校园管理系统 目录 基于springbootvue的校园管理系统 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c…

测评造假?Mistral首个多模态模型Pixtral 12B发布

测评造假&#xff1f;Mistral首个多模态模型Pixtral 12B发布&#xff01; 近日&#xff0c;法国人工智能&#xff08;AI&#xff09;初创公司Mistral于9月11日宣布推出其首款多模态AI大模型——Pixtral 12B&#xff0c;成功吸引了全球科技界的广泛关注。这款集图像与文本处理能…

如何利用 CSS 渐变实现多样化背景效果

前言 总在平常看到像这样的图片 背景是如何实现的呢 背景效果的多样性和美观性直接影响用户体验。CSS 渐变为设计师提供了一种强大且灵活的方法来创建引人注目的背景。渐变是颜色之间平滑过渡的效果&#xff0c;通过调整渐变类型和设置&#xff0c;你可以轻松实现从简单到复杂…

【c++实现】统计上升四元组

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 &#x1f308;C专栏&#xff1a;C 文章目录 1. 题目描述2. 解释3. DP前缀和枚举 1. 题目描…

Linux系统使用Docker安装DockerUI并实现远程管理本地容器无需公网IP

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

超详细、史上最全pytorch安装教程

一、anaconda安装 1.下载 Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirrorhttps://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 这里划到最下面选择5.3.1最新版&#xff1a; 2.下载完成后安装 点击next 点击 I agree 选择All Us…

AI虚拟人的发展与挑战:表象与背后的技术瓶颈

随着人工智能技术的快速进步&#xff0c;AI虚拟人作为一种新兴技术应用&#xff0c;正在逐渐渗透到影视制作、娱乐、教育、商业等多个领域。通过AI技术生成虚拟角色并使其能够在数字环境中与人类进行互动&#xff0c;这种技术无疑展示了未来虚拟社交和虚拟体验的广阔前景。无论…

C++ namespace(域)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 namespace的价值 避免命名冲突&#xff1a;在大型项目或使用多个库的情况下&#xff0c;不同部分可能会定义相同名称的实体&#xff08;如变量、函数、类等&a…

Leetcode 109.有序链表转换二叉搜索树(Medium)

给定一个单链表的头节点 head &#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为 平衡 二叉搜索树。 示例 1: 输入: head [-10,-3,0,5,9] 输出: [0,-3,9,-10,null,5] 解释: 一个可能的答案是[0&#xff0c;-3,9&#xff0c;-10,null,5]&#xff0c;它表示所示的高度…

js TypeError: Cannot read property ‘initialize’ of undefined

js TypeError: Cannot read property ‘initialize’ of undefined 在JavaScript开发旅程中&#xff0c;遇到TypeError: Cannot read property ‘initialize’ of undefined这样的错误提示&#xff0c;无疑是令人沮丧的。这个错误通常意味着你试图访问一个未定义对象的initiali…

【开端】docker基线漏洞修复

一、限制容器之间的网络流量 vim /etc/docker/daemon.json 添加以下内容到 daemon.json 文件中&#xff1a; { "icc": false } sudo service docker restart docker restart apollo-configservice docker restart apollo-portal docker restart apo…

C++的流提取(>>)(输入) 流插入(<<)(输出)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 什么是输入和输出流 流提取&#xff08;<<&#xff09;(输入) 理解&#xff1a;我们可以理解为&#xff0c;输入到io流里面&#xff0c;比如是cin&…

C语言初识编译和链接

目录 翻译环境和运行环境编译环境预编译编译词法分析语法分析语义分析 汇编 链接运行环境 翻译环境和运行环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令&#xff08;⼆进制指令&…

web基础之SSRF

1、内网访问 题目提示&#xff1a;访问位于127.0.0.1的flag.php&#xff1b;直接利用ssrf漏洞访问?url127.0.0.1/flag.php 2、伪协议读取文件 &#xff08;1&#xff09;题目提示&#xff1a;尝试去读取一下Web目录下的flag.php吧 &#xff08;2&#xff09;什么是伪协议&a…

写的一致性问题之失效模式

文章目录 1、先删除redis缓存&#xff0c;再写入mysql&#xff1a;1.1、高并发情况下分析出现的问题 1、先删除redis缓存&#xff0c;再写入mysql&#xff1a; 此时删除redis成功&#xff0c;写入mysql成功&#xff0c;此时redis是空&#xff0c;mysql是新数据。此时删除redis…

C#匿名方法

在C#中&#xff0c;匿名函数是一种没有名字的方法&#xff0c;可以在代码中定义和使用。 我们已经提到过&#xff0c;委托是用于引用与其具有相同标签的方法。换句话说&#xff0c;可以使用委托对象调用可由委托引用的方法。 匿名方法&#xff08;Anonymous methods&#xff…

基于CNN卷积神经网络迁移学习的图像识别实现

基于CNN卷积神经网络迁移学习的图像识别实现 基于CNN卷积神经网络迁移学习的图像识别实现写在前面一&#xff0c;原理介绍迁移学习的基本方法1.样本迁移&#xff08;Instance based TL&#xff09;2.特征迁移&#xff08;Feature based TL&#xff09;3.模型迁移&#xff08;Pa…

【Qt应用】Qt编写简易登录注册界面

目录 引言 一、准备工作 二、设计思路 2.1 登录 2.2 注册 三、登录功能 3.1 创建账号与密码输入框 3.2 设置密码输入框格式 3.2.1 初始 3.2.2 创建一个显示隐藏按钮 3.2.3 信号与槽函数 3.3 创建登录按钮 3.4 创建可选功能 四、注册功能 4.1 账号和密码输入…

烟囱的高度

已知人离烟囱的距离为 a&#xff0c;人的眼睛离地高度为 b&#xff0c;人看烟囱顶时的仰角为 θ&#xff0c;求烟囱的高度 h。 输入格式 a b θ 注&#xff1a;a、b 的单位为米&#xff0c;均为小数。仰角 θ 的单位为角度&#xff0c;包括度、分和秒数&#xff0c;其中度、分为…