Makefile学习总结

news2024/12/27 12:08:31

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 ?= -O2

SOURCES = main.c foo.c bar.c
LDFLAGS ?= -lm

all: program

program: $(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.o

main.o : main.c
gcc -c -o main.o main.c

sub.o : sub.c
gcc -c -o sub.o sub.c

clean:
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.h

clean:
rm *.o test -f

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

objs := main.o sub.o

test : $(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 : %.c
	gcc -Wp,-MD,.$@.d -c -o $@ $<

clean:
	rm *.o test -f

distclean:
	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.c
      	gcc -c -o main.o main.c
      
    • 对于sub.o这个规则就是:

    • sub.o:sub.c
      	gcc -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_SUB3

obj-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)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include

LDFLAGS := 

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

TARGET := test


obj-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.c
	printf("%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.c
		printf("%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.coloradmin.cn/o/2124123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DAY73

作业 pro文件&#xff1a; QT texttospeech 头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> //按钮类 #include <QLabel> //标签类 #include <QLineEdit> //行编译器类 #include…

【delphi】判断多显示器下,程序在那个显示器中

在 Delphi 中&#xff0c;如果你的电脑连接了多个显示器&#xff0c;可以通过以下步骤判断某个程序在哪个显示器上运行。 方法概述&#xff1a; 获取程序窗口的位置&#xff08;例如窗体的 Left、Top 坐标&#xff09;。使用 Screen.MonitorFromWindow 函数来确定该窗口所属的…

Hibernate QueryPlanCache 查询计划缓存引发的内存溢出

目录 1.排查方式2.结论3.解决办法 前言&#xff1a;在生产环境中有一个后端程序多次报oom然后导致程序中断。 1.排查方式 通过下载后端程序产生的oom文件&#xff0c;将oom文件导入MemoryAnalyzer程序分析程序堆内存使用情况。 1、将oom文件导入MemoryAnalyzer后可以看到概览信…

在银河麒麟服务器操作系统中设置SSH登录限制

在银河麒麟服务器操作系统中设置SSH登录限制 1、引言2、 步骤一&#xff1a;检查MaxStartups选项3、步骤二&#xff1a;修改MaxStartups选项4、步骤三&#xff1a;重启SSH服务 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、引言 在服务…

flask旧衣物捐赠系统—计算机毕业设计源码26577

摘要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作规…

【干货分享】Ftrans安全数据交换系统 搭建跨网数据传输通道

安全数据交换系统是一种专门设计用于在不同的网络、系统或组织之间安全地传输数据的软件或硬件解决方案。这种系统通常包含多种安全特性&#xff0c;以确保数据在传输过程中的保密性、完整性和可用性。 安全数据交换系统可以解决哪些问题&#xff1f; 安全数据交换系统主要解…

神经网络卷积层和最大池化

文章目录 一、卷积层原理二、相关函数的概念三、卷积层的应用四、最大池化原理五、最大池化案例 一、卷积层原理 ./ 当前目录&#xff1b;…/ 上级目录 父类&#xff08;也称为基类或超类&#xff09;是指在类继承体系中被其他类继承的类。也就是被其他子类进行调用的类 当In_…

「豆包 Marscode 体验官」AI 加持的云端 IDE——三种方法高效开发前后端聊天交互功能

以下是「豆包 MarsCode 体验官」优秀文章&#xff0c;作者努力的小雨。 豆包 MarsCode 豆包MarsCode 编程助手支持的 IDE: 支持 Visual Studio Code 1.67.0 及以上版本&#xff0c;以及 JetBrains 系列 IDE&#xff0c;如 IntelliJ IDEA、Pycharm 等&#xff0c;版本要求为 22…

016.PL-SQL编程—过程

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

服务器测试之GPU基础汇总

GPU基础汇总 1.GPU简介 1.1.什么是GPU GPU英文全称Graphic Processing Unit&#xff0c;中文翻译为“图形处理器”。一个专门的图形核心处理器。GPU是显示卡的“大脑”&#xff0c;决定了该显卡的档次和大部分性能&#xff0c;同时也是2D显示卡和3D显示卡的区别依据。可以形…

Flask 第三课 -- 第一个应用

上一章节我们已经成功安装了 Flask&#xff0c;接下来我们可以创建一个简单的 Flask 应用。 首先&#xff0c;创建一个名为 app.py 的文件&#xff0c;并添加以下内容&#xff1a; from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return Hello,…

网络拓扑结构介绍

这张图展示了一个复杂的网络拓扑结构&#xff0c;它包括了多个运营商的接入、负载均衡、安全防护以及数据处理等多个关键环节。整个网络通过精心设计的架构和高效的节点连接&#xff0c;实现了数据的快速传输和安全处理。 一、各个模块介绍 运营商接入&#xff1a; 移动、电信…

论文速读|形机器人的高速和抗冲击远程操作

论文地址&#xff1a;https://arxiv.org/pdf/2409.04639 本文提出了一种综合解决方案&#xff0c;用于远程控制类人机器人&#xff0c;实现了高速度和冲击抵抗的操作。通过结合无校准的运动捕捉和重定标、低延迟全身运动流式传输工具箱和高带宽的摆线驱动器&#xff0c;显著提高…

【Python报错已解决】ValueError: All arrays must be of the same length

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;调整数组长度2.2 步骤二…

数据流图的例题2

答案选B 解析&#xff1a; 第一个错误&#xff0c;E1和E2是外部实体&#xff0c;实体之间不可能有数据传输&#xff0c;DF2错误 第二个错误&#xff0c;DF6是外部实体把数据传到存储。外部实体必须把数据传到加工&#xff0c;进行加工之后才能对数据存储&#xff0c;DF6错误…

Git环境搭建

我的博客大纲 我的GIT学习大纲 Git安装步骤&#xff1a; 1.官网地址 查看 GNU 协议&#xff0c;可以直接点击下一步&#xff1a; 2.Git配置选项如下&#xff1a; 3.选择后台客户端连接协议&#xff0c;选默认值 OpenSSL&#xff0c;然后下一步。 4.Git换行符号 5.选择终端类型…

Lesson08---string类(2)

1.assign assign的功能就类似于把string里面原来有的东西清空然后重新赋值 但是重新赋值也可以达到一样的效果感觉没什么用&#xff0c;了解一下就行 2.insert 第一个参数是在第几个位置插入&#xff0c;第二个参数是插入的字符串 但是这里不得不吐槽一下这里只能是字符串&…

Python之异常处理与程序调试(Exception Handling and Program Debugging in Python)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Netty权威指南:Netty总结-服务端创建

第13章 服务端创建 13.1 原生NIO类库复杂性 开发高质量的NIO的程序并不简单&#xff0c;成本太高 13.2 服务端创建源码 通过ServerBootStrap启动辅助类启动Netty 13.2.1 创建时序图 以下是关键步骤&#xff1a; 创建ServerBootStrap实例。这是启动辅助类&#xff0c;提供一…

AI写作培训课创业参考模式:《如何与AI共同写作》

在数字化时代,写作能力已成为职场和生活中不可或缺的一项技能。随着人工智能技术的发展,AI工具开始在写作过程中发挥越来越重要的作用。《如何与AI共同写作》正是这样一门专业的在线写作课程,它通过结合AI技术和实践操作,帮助学员在30天内掌握高效的写作技巧,提升个人品牌…