Makefile入门

Makefile入门

文章目录

  • Makefile入门
    • 一、Makefile入门
      • 1.1 编译工具及构建工具介绍:
      • 1.2 编译的四个阶段:
      • 1.3 Makefile的认知:
        • 1.3.1 什么是Makefile:
        • 1.3.2 Makefile的规则与示例:
    • 二、Makefile的基本语法:
        • 2.1 通配符:
        • 2.2 伪目标:
        • 2.3 注释和换行符:
        • 2.4 变量赋值和预定义变量:
        • 2.5变量的替换引用:
      • 2.6 Makefile的常用函数:
        • 2.6.1 Makefile函数的格式:
        • 2.6.2 wildcard 通配符:
        • 2.6.3 shell函数:
        • 2.6.4 patsubst替换函数:
        • 2.6.5 subst替换函数:
        • 2.6.6 dir函数:
        • 2.6.7 notdir函数:
        • 2.6.8 suffix函数:
        • 2.6.9 basename函数:
        • 2.6.10 addsuffix函数:
        • 2.6.11 addprefix函数:
        • 2.6.12 foreach函数:
      • 2.7 条件判断语句:
        • 2.7.1 条件判断ifeq 语句:
          • 2.7.1.1 ifeq 语句的语法:
          • 2.7.1.2 ifeq 语句的实例:
        • 2.7.2 条件判断ifneq语句:
          • 2.7.2.1 ifneq语句的语法:
          • 2.7.2.2 ifneq语句的实例:
        • 2.7.3 条件判断ifdef语句:
          • 2.7.3.1 ifdef语句的语法:
          • 2.7.3.2 ifdef语句的实例:
        • 2.7.4 条件判断ifndef语句:
          • 2.7.4.1 ifndef语句的语法:
          • 2.7.4.2 ifndef语句的实例:
          • 2.7.4.2 ifndef语句的实例:

一、Makefile入门

1.1 编译工具及构建工具介绍:

在之前的课程,都是直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性make工具就根据makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。
在这里插入图片描述

cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文件,例如Makefile、Visual Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还提供了一些高级功能,如测试、打包、安装等。

qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程文件。

ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写配置文件,而是由其他构建系统(如cmake)来生成

auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,auto conf是一个用于生成configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的Makefile文件的脚本Makefile.am是一种用于auto make的配置文件格式,它包含了一些指令和变量,用于定义程序或库的源文件、目标文件、依赖关系和编译选项等。

make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。

1.2 编译的四个阶段:

回顾下编译的四个过程:预处理(Pre-Processing)编译(Compiling)汇编 (Assembliang)链接(Linking)

在这里插入图片描述

1.3 Makefile的认知:

1.3.1 什么是Makefile:

​ 相信在Linux系统中经常会用到make这个命令来编译程序,而执行make命令所依赖的文件便是Makefile文件,make命令通过Makefile文件编写的内容对程序进行编译。make命令根据文件更新的时间戳来决定哪些文件需要重新编译这可以避免编译已经编译过的,没有改变的文件,从而提升编译效率。

1.3.2 Makefile的规则与示例:

一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:

目标(target)…: 依赖()…
<tab>命令(command)

当“依赖文件”比“目标文件”更加新时,或者目标文件还没有生成时,就会执行“命令”

Makefile一个示例:

debug :@echo "hello world"

在这里插入图片描述

如果我们要编译下面一个简单的例子:

#include <stdio.h>int main()
{printf("Hello World!");return 0;
}

Makefile修改如下:

debug :@echo "hello world"hello : hello.cgcc hello.c -o hello

执行命令make hello可以生成 hello文件, 执行make debug可以输出“hello world”:

在这里插入图片描述

Makefile修改如下:

debug :@echo "hello world"	hello : hello.c@gcc hello.c -o helloclean :@rm hello

执行命令make clean可以删除hello程序:

在这里插入图片描述

以上述为例,介绍一下详细规则:

  • 目标(target)通常是要生成的文件的名称可以是可执行文件(比如上例中的hello就是要生成的可执行文件名)或OBJ 文件, 也可以是一个执行的动作名称(如上述例子中的clean)。
  • 依赖(prerequiries)是用来产生目标的材料(比如源文件 ) ,一个目标经常有几个依赖
  • 命令(command)是生成目标时执行的动作,一个规则可以含有几个命令,每个命令占一 行
  • 每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。通常当一个依赖发生了变化,规则就会调用命令产生新的目标
  • 但是并非所有目标都有依赖,像上述例子中的“clean”,它只负责清除文件。
  • 每一个Makefile文件也可以包含规则外的其他文件
  • 对于上面的Makefile,执行"make"后仅当hello.c文件比hello文件新时,才会执行gcc hello.c -o hello从而生成hello文件,如果没有hello文件时,这个命令也会被执行。运行“make clean”时,由于clean目标没有依赖,它的命令“rm -f hello”就会被强制执行。

二、Makefile的基本语法:

2.1 通配符:
  • 当一个目标文件所依赖的依赖文件有很多时,需要写很多条规则,因此可以使用通配符只需要写一行来代替多行的规则。

首先举一个没有使用通配符的例子:共有两个.c文件,分别为a.c,b.c:

/* a.c */
#include <stdio.h>int main()
{func_b();return 0;
}
/* b.c */
#include <stdio.h>void func_b()
{printf("This is function b");
}
# 这是一个 Makefile 文件,用于编译程序。
# 该 Makefile 包含两个源文件 a.c 和 b.c,以及一个目标文件 test。
# 编译命令为:
# make test
# 运行命令为:
# ./testtest:a.o b.ogcc -o test a.o b.oa.o : a.cgcc -c -o a.o a.cb.o : b.cgcc -c -o b.o b.c

上述例子中的目标文件“test”有两个依赖“a.o”,“b.o”,不难看出,生成a.o,b.o的规则有很多相似之处,因此可以使用通配符来改进Makefile。改进后的Makefile如下:

# 这是一个Makefile文件,用于编译程序。test:a.o b.ogcc -o test $^%.o : %.cgcc -c -o $@ $<

其中用到的通配符的解释:

  • %.o:表示所有.o文件
  • %.c:表示所有.c文件
  • $@:表示目标
  • $<:表示第一个依赖文件
  • $^:表示所有依赖文件
2.2 伪目标:

如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执行其命令。通过将目标声明为伪目标 .PHONY: hello,可以避免这种情况,强制执行其命令

debug :@echo "hello world"	hello :@gcc hello.c -o helloclean :@rm hello.PHONY : hello

在这里插入图片描述

2.3 注释和换行符:
  • 采用#进行一行注释
  • 采用\作为续行符

在这里插入图片描述

2.4 变量赋值和预定义变量:
  • Makefile中的变量赋值运算符有四种,分别是=:=?=+=$符号表示取变量的值,当变量名多于一个字符时,使用"( )":

= 表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。例如,VAR_A = A,VAR_B = $(VAR_A) B,VAR_A = AA,那么最后VAR_B的值是AA B,而不是A B。

:= 表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。例如,VAR_A := A, VAR_B := $(VAR_A) B,VAR_A := AA,那么最后VAR_B的值是A B,而不是AA B。

?=表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。例如,VAR ?= new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,否则保持原来的值不变。

+= 表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。例如,VAR += new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,如果VAR在之前被赋值为old_value,那么VAR的值就为old_value new_value

  • $符的其他用法:

$^ 表示所有的依赖文件

$@ 表示生成的目标文件

$< 代表第一个依赖文件

在这里插入图片描述

2.5变量的替换引用:
$(var:a=b)或${var:a=b}			#变量的替换引用的语法格式

表示把变量var的值中的a后缀替换成b后缀。例如:

src := a.c b.c c.c
obj := $(src:c=o)

把变量src的值中的.c后缀替换成.o后缀,赋值给变量obj,结果是:

obj := a.o b.o c.o
# 这是一个 Makefile 文件,用于编译和运行 C 语言程序。TARGET = hello					# 定义一个变量 TARGET,其值为 hello
CC := gcc 						# 定义一个变量 CC,其值为 gcc
TARGET1 := $(CC) $(TARGET)		# 定义一个变量 TARGET1,其值为 $(CC) $(TARGET)
CC ?= g++						# 定义一个变量 CC,其值为 g++,如果 CC 未定义,则使用 g++
CC += -g						# 追加参数 -g 到 CC 变量中SRC = hello.c					# 定义一个变量 SRC,其值为 hello.c
OBJ = $(SRC:.c=.o)				# 定义一个变量 OBJ,其值为 hello.c 编译成的 .o 文件debug :@echo "hello world"	@echo $(TARGET1)@echo $(OBJ)hello :	$(OBJ)					# 定义一个规则,其目标为 hello,依赖于 $(OBJ) 文件			@$(CC) hello.c -o 	\$(TARGET)		clean :@rm hello.PHONY : hellos

2.6 Makefile的常用函数:

2.6.1 Makefile函数的格式:

Makefile函数的基本格式是: ( f u n c t i o n a r g u m e n t s ) ∗ ∗ ,或者是 ∗ ∗ (function arguments)**,或者是** (functionarguments),或者是{function arguments},其中,function 是函数名arguments 是函数的参数参数之间要用逗号分隔开,参数和函数名之间使用空格分开调用函数的时候要使用字符“$”后面可以跟小括号或者大括号。

2.6.2 wildcard 通配符:

Makefile中的wildcard 是一个函数,用于扩展通配符返回与通配符匹配的文件列表通配符是一种特殊的字符,可以表示多个文件名或目录名,常见的通配符有 *?分别表示任意长度的任意字符和单个任意字符。格式如下:

$(wildcard arguments)

比如*.c 表示所有以 .c 结尾的文件名,a?.txt 表示所有以 a 开头,中间有一个任意字符,以 .txt 结尾的文件名。例如:

# 这是一个Makefile文件,用于编译当前目录下的所有.c文件。files = $(wildcard *.c)all: @echo files = $(files)

这段Makefile代码的主要功能是:

  • 扫描当前目录:使用wildcard函数扫描当前目录下所有的.c文件,并将这些文件名存储在变量files中。
  • 输出文件列表:定义了一个名为all的目标,当用户执行make all命令时,会输出变量files的值,即当前目录下所有.c文件的列表。

在这里插入图片描述

2.6.3 shell函数:

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

$(shell 命令)

这里是一个简单的例子,使用shell函数来获取当前目录下的文件列表,并将结果赋值给变量:

# 这是一个Makefile文件,用于列出当前目录下的文件。  
FILES := $(shell ls)  all:  @echo "Current directory files:" $(FILES)  

在这里插入图片描述

2.6.4 patsubst替换函数:

在Makefile中,patsubst 函数用于模式替换,它的原型如下:

$(patsubst 模式,替换文本,文本)

这个函数会在“文本”中查找与“模式”匹配的部分,并将其替换为“替换文本”。模式可以使用通配符,如%表示任意数量的字符。

下面是一个使用patsubst函数的实例:

# 假设我们有一个变量,包含了文件名列表,文件名以.c结尾  
SRCS := a.c b.c c.c  										# 假设文件名列表为a.c b.c c.c  # 我们想要将这些文件名中的.c替换为.o,以得到对象文件列表  
OBJS := $(patsubst %.c,%.o,$(SRCS))  						# 使用patsubst函数替换.c为.o  all:  @echo OBJS = $(OBJS)  							

在这个例子中,SRCS变量包含了三个以.c结尾的文件名。我们使用patsubst函数将这些文件名中的.c替换为.o,并将结果赋值给OBJS变量。执行这个Makefile得出结果:

在这里插入图片描述

2.6.5 subst替换函数:

在Makefile中,subst 函数用于字符串替换,它的原型如下:

$(subst 从,到,文本)

这个函数会在“文本”中查找所有的“从”字符串,并将其替换为“到”字符串patsubst不同,subst不使用模式匹配,而是进行简单的字符串替换。

下面是一个使用subst函数的实例:

# 假设我们有一个变量,包含了一些文本  
TEXT := hello world, welcome to the makefile world  # 我们想要将所有的"world"替换为"Makefile"  
REPLACED_TEXT := $(subst world,Makefile,$(TEXT))  all:  @echo "Original text: $(TEXT)"  @echo "Replaced text: $(REPLACED_TEXT)"

在这个例子中,TEXT变量包含了一段文本。我们使用subst函数将这段文本中所有的“world”字符串替换为“Makefile”,并将结果赋值给REPLACED_TEXT变量。最后,我们打印出原始文本和替换后的文本。

在这里插入图片描述

2.6.6 dir函数:

在Makefile中,dir 函数用于处理文件名和路径,提取目录部分。以下是这个函数的原型和实例:

$(dir <names...>)
  • <names...>:一个或多个文件名。
  • 返回值:文件名中的目录部分。
# 定义包含完整路径的文件名变量  
FULL_PATHS := /path/to/file1.txt /another/path/to/file2.txt  # 使用dir函数提取目录部分  
DIRS := $(dir $(FULL_PATHS))  # 打印结果  
all:  @echo "Full paths: $(FULL_PATHS)"  @echo "Directories: $(DIRS)"

这段代码的主要功能是:

  • 定义一个包含多个文件完整路径的变量 FULL_PATHS
  • 使用 dir 函数从这些完整路径中提取目录部分,并将结果存储在变量 DIRS 中。
  • 通过定义一个 all 目标,打印出原始的完整路径和提取出的目录部分,以便用户查看和验证结果。

通过这种方式,代码展示了如何在 Makefile 中处理文件路径,并提取目录部分,这对于文件管理和路径操作非常有用。

在这里插入图片描述

2.6.7 notdir函数:

在Makefile中,notdir 函数用于处理文件名和路径,去除目录部分。以下是这个函数的原型和实例:

$(notdir <names...>)
  • <names...>:一个或多个文件名。
  • 返回值:文件名中的非目录部分。
# 定义包含完整路径的文件名变量  
FULL_PATHS := /path/to/file1.txt /another/path/to/file2.txt  # 使用notdir函数去除目录部分  
FILES := $(notdir $(FULL_PATHS))  # 打印结果  
all:  @echo "Full paths: $(FULL_PATHS)"  @echo "Files: $(FILES)"

这段 Makefile 代码的主要功能是:

  • 定义一个包含文件完整路径的变量 FULL_PATHS
  • 使用 notdir 函数从 FULL_PATHS 中提取文件名,去除路径部分,并将结果存储在 FILES 变量中。
  • 通过定义 all 目标,打印 FULL_PATHSFILES 变量的值,以便用户查看文件的完整路径和去除路径后的文件名。

简而言之,这段代码的目的是展示如何从文件的完整路径中提取文件名,并通过打印结果验证操作的正确性。

在这里插入图片描述

2.6.8 suffix函数:

Makefile的suffix函数用于获取文件名中的后缀部分。以下是关于suffix函数的原型和如何在Makefile中使用它的一个简单示例:

$(suffix <names...>)
  • <names...>:一个或多个文件名。
  • 返回值:文件名中的后缀部分。
# 定义包含文件名和后缀的变量  
FILES := file1.txt file2.c file3.o  # 使用suffix函数提取文件后缀  
SUFFIXES := $(suffix $(FILES))  # 打印结果  
all:  @echo "File suffixes: $(SUFFIXES)"

这段 Makefile 代码的主要功能是提取并显示一组文件名的后缀。具体步骤如下:

  1. 定义一个包含多个文件名的变量 FILES
  2. 使用 suffix 函数提取这些文件名的后缀,并将结果存储在变量 SUFFIXES 中。
  3. 通过定义一个 all 目标,使用 @echo 命令打印出提取到的文件后缀。

通过这段代码,用户可以方便地获取一组文件的后缀信息,并将其显示在终端上。

在这里插入图片描述

2.6.9 basename函数:

basename函数格式:

$(basename names...)

功能:从文件名序列中取出各个文件名的前缀部分

返回值:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。

dep = $(basename src/foo.c src-1.0/bar hacks.o)	all:@echo dep = $(dep)

在这里插入图片描述

2.6.10 addsuffix函数:
$(addsuffix suffix,names...)

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

# 这是一个Makefile文件,用于给cat和dog两个文件生成.c文件dep = $(addsuffix .c,cat dog)                # 生成.c文件列表all:@echo $(dep)
2.6.11 addprefix函数:

在Makefile中,addprefix函数用于给字符串列表中的每一个字符串添加前缀。这个函数在处理文件名列表、变量等场景下非常有用。

$(addprefix <prefix>,<names>...)
  • <prefix>:要添加到每个字符串前面的前缀。
  • <names>:一个或多个字符串,每个字符串都会添加前缀。

假设我们有一个文件名列表,想要给每个文件名前面添加路径src/,可以使用addprefix函数实现:

# 定义文件名列表  
FILES := file1.c file2.c file3.c  # 使用addprefix给文件名添加前缀  
SRC_FILES := $(addprefix src/,$(FILES))  # 打印结果  
all:  @echo $(SRC_FILES)

在这里插入图片描述

在这个例子中,SRC_FILES变量会被赋值为src/file1.c src/file2.c src/file3.c,实现了给文件名列表添加前缀的目的。s

2.6.12 foreach函数:

在GNU Make中,foreach函数是一个用于迭代处理的函数,它允许你对列表中的每个元素执行一系列操作。不过需要注意的是,foreach并不是Makefile的内置函数,而是GNU Make提供的扩展功能。

$(foreach <var>,<list>,<text>)
  • <var>:迭代过程中当前元素的变量名。
  • <list>:要进行迭代的列表。
  • <text>:对于列表中每个元素要执行的文本(通常是一系列命令或Makefile语句)。

以下是一个使用foreach函数的示例,该示例将遍历一个文件名列表,并为每个文件名添加后缀.bak

# 定义文件名列表  
FILES := file1 file2 file3  # 使用foreach为文件名添加后缀  
BACKUP_FILES := $(foreach f,$(FILES),$(f).bak)  # 打印结果  
all:  @echo $(BACKUP_FILES)

在这个例子中,BACKUP_FILES变量会被赋值为file1.bak file2.bak file3.bak,这是通过将.bak后缀添加到FILES列表中的每个文件名来实现的。

当你运行make命令时,它将执行all目标,并打印出带有.bak后缀的文件名列表。

在这里插入图片描述

2.7 条件判断语句:

2.7.1 条件判断ifeq 语句:

在Makefile中,ifeq语句用于进行条件判断,它比较两个参数是否相等,如果相等,则执行随后的代码块。这是Makefile中常用的条件语句之一,用于根据不同的条件来调整构建过程。

2.7.1.1 ifeq 语句的语法:
ifeq (<arg1>, <arg2>)  
# 如果<arg1>等于<arg2>,则执行这里的命令  
endif
  • <arg1><arg2>是要进行比较的两个参数,它们可以是变量、字符串或者数值。
  • 如果<arg1><arg2>相等,那么ifeqendif之间的命令将会被执行。
  • endif是条件语句的结束标志。
2.7.1.2 ifeq 语句的实例:

以下是一个使用ifeq语句的示例,该示例根据编译器的类型来设置不同的编译选项。

CC=gcc  # 检查编译器是否为gcc  
ifeq ($(CC),gcc)  CFLAGS=-O2  
else  CFLAGS=-O0  
endif  all:  $(CC) $(CFLAGS) -o myprogram myprogram.c

在这个示例中,如果变量CC的值是gcc,那么CFLAGS将被设置为-O2,否则CFLAGS将被设置为-O0。然后,使用这些设置来编译程序。

2.7.2 条件判断ifneq语句:

在Makefile中,ifneq语句是用于条件判断的关键字,它用于比较两个参数是否不相等如果两个参数不相等,那么ifneqendif之间的代码块将被执行

2.7.2.1 ifneq语句的语法:
ifneq (<arg1>, <arg2>)  
# 如果<arg1>不等于<arg2>,则执行这里的命令  
endif
  • <arg1><arg2>是要进行比较的两个参数。
  • 如果<arg1><arg2>不相等,那么ifneqendif之间的命令将会被执行。
2.7.2.2 ifneq语句的实例:

下是一个使用ifneq语句的示例,该示例根据操作系统的类型来设置不同的编译选项。

# 获取操作系统类型  
OS=$(shell uname -s)  # 检查操作系统是否为Linux  
ifneq ($(OS),Linux)  CFLAGS=-DNOT_LINUX  
else  CFLAGS=-DLINUX  
endif  all:  gcc $(CFLAGS) -o myprogram myprogram.c

在这个示例中,OS变量被设置为当前操作系统的类型。然后,使用ifneq语句检查OS是否不等于Linux。如果OS的值不是Linux,那么CFLAGS将被设置为-DNOT_LINUX;否则,CFLAGS将被设置为-DLINUX。最后,使用这些设置来编译程序。

2.7.3 条件判断ifdef语句:

在Makefile中,ifdef语句用于检查某个变量是否已被定义如果变量已定义,那么ifdefendif之间的代码块将被执行

2.7.3.1 ifdef语句的语法:
ifdef <variable-name>  
# 如果<variable-name>已定义,则执行这里的命令  
endif
  • <variable-name>是要检查的变量名。
2.7.3.2 ifdef语句的实例:

以下是一个使用ifdef语句的示例,该示例根据是否定义了某个变量来决定是否包含特定的源文件。

# 假设有一个可选的变量FEATURE  
# make FEATURE=1将会定义这个变量  ifdef FEATURE  
SRCS += feature.c  
endif  all:  gcc $(SRCS) -o myprogram

在这个示例中,如果FEATURE变量被定义(例如,通过在命令行上执行make FEATURE=1),那么SRCS变量将包含feature.c,否则不包含。然后,使用SRCS变量中列出的源文件来编译程序。

2.7.4 条件判断ifndef语句:

在Makefile中,ifndef语句用于检查某个变量是否被定义。如果变量未定义,那么ifndefendif之间的代码块将被执行。

2.7.4.1 ifndef语句的语法:
ifndef <variable-name>  
# 如果<variable-name>未定义,则执行这里的命令  
endif
  • <variable-name>是要检查的变量名。
2.7.4.2 ifndef语句的实例:

以下是一个使用ifndef语句的示例,该示例根据是否未定义某个变量来决定是否包含特定的源文件。

# 假设有一个可选的变量DEBUG  
# 如果未定义DEBUG,则不包含debug.c源文件  ifndef DEBUG  
SRCS := main.c utils.c  
else  
SRCS := main.c utils.c debug.c  
endif  all:  gcc $(SRCS) -o myprogram

all:
gcc $(SRCS) -o myprogram


在这个示例中,如果`FEATURE`变量被定义(例如,通过在命令行上执行`make FEATURE=1`),那么`SRCS`变量将包含`feature.c`,否则不包含。然后,使用`SRCS`变量中列出的源文件来编译程序。#### 2.7.4 条件判断ifndef语句:在Makefile中,`ifndef`语句用于检查某个变量是否**未**被定义。如果变量未定义,那么`ifndef`和`endif`之间的代码块将被执行。##### 2.7.4.1 ifndef语句的语法:```makefile
ifndef <variable-name>  
# 如果<variable-name>未定义,则执行这里的命令  
endif
  • <variable-name>是要检查的变量名。
2.7.4.2 ifndef语句的实例:

以下是一个使用ifndef语句的示例,该示例根据是否未定义某个变量来决定是否包含特定的源文件。

# 假设有一个可选的变量DEBUG  
# 如果未定义DEBUG,则不包含debug.c源文件  ifndef DEBUG  
SRCS := main.c utils.c  
else  
SRCS := main.c utils.c debug.c  
endif  all:  gcc $(SRCS) -o myprogram

在这个示例中,如果DEBUG变量未定义,SRCS变量将只包含main.cutils.c。如果DEBUG变量已定义,SRCS变量还将包含debug.c。然后,使用SRCS变量中列出的源文件来编译程序。****

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

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

相关文章

Java注解和JDK新特性

1. 注解 1.1. 认识注解 Annotation&#xff1a;JDK1.5新提供的技术 编译检查&#xff1a;比如SuppressWarnings, Deprecated和Override都具有编译检查的作用替代配置文件&#xff1a;使用反射来读取注解的信息 注解就是代码里的特殊标记&#xff0c;用于替代配置文件&#…

内存管理篇-17解开页表的神秘面纱-下

1.页表初探遗留问题-页表的创建过程 使用MMU之前&#xff0c;页表要准备好&#xff0c;怎么准备的&#xff1f;如何把物理内存通过section映射构建页表页表的创建过程分析&#xff1a;__create_page_tables--创建临时页表&#xff0c;然后在开启MMU 页表的大小和用途页表在内存…

zdppy_cache缓存框架升级,支持用户级别的缓存隔离,支持超级管理员管理普通用户的缓存

启动服务 import zdppy_api as api import zdppy_cachekey1 "admin" key2 "admin"app api.Api(routes[*zdppy_cache.zdppy_api.cache(key1, key2, api) ])if __name__ __main__:import zdppy_uvicornzdppy_uvicorn.run(app, host"0.0.0.0",…

Mac 安装Hadoop教程

1. 引言 本教程旨在介绍在Mac 电脑上安装Hadoop&#xff0c;便于编程开发人员对大数据技术的熟悉和掌握。 2.前提条件 2.1 安装JDK 想要在你的Mac电脑上安装Hadoop&#xff0c;你必须首先安装JDK。具体安装步骤这里就不详细描述了。你可参考Mac 安装JDK8。 2.2 配置ssh环境…

代码随想录 -- 字符串 -- 重复的子字符串

459. 重复的子字符串 - 力扣&#xff08;LeetCode&#xff09; 暴力解法&#xff1a; 思路&#xff1a; 假设子串 s 长度 n 为 i&#xff0c;从1到n/2遍历&#xff1a; 1. 如果 s 能够由他的子串重复构成&#xff0c;那么 s 的长度 n 一定整除其子串 s 的长度 n&#xff0c; …

结合Wireshark抓包实战,图文详解TCP三次握手及四次挥手原理(附下载)

网络安全的基础是网络&#xff0c;若连最基础的网络协议都搞不明白&#xff0c;何谈网络安全。针对核心的TCP协议&#xff0c;本文通过Wireshark工具抓取并分析TCP三次握手和四次挥手的详细过程&#xff0c;包括数据包捕获步骤&#xff0c;每个握手阶段和挥手阶段的数据包内容解…

数据分析处理库(pandas)

目录 数据预处理 数据读取 DataFrame结构 数据索引 创建DataFrame Series操作 数据分析 统计分析 pivot数据透视表 groupby操作 常用函数操作 Merge操作 排序操作 缺失值处理 apply自定义函数 时间操作 绘图操作 大数据处理技巧 数值类型转换 属性类型转换…

MediaGo下载器:专业级功能,轻松应对各种下载需求!

前言 在科技的浪潮中有这样一句名言&#xff0c;深刻地揭示了创新的力量&#xff1a;“创新是引领发展的第一动力。”正是这股不竭的动力&#xff0c;推动了无数软硬件产品的诞生与迭代&#xff0c;为我们带来了前所未有的便捷与体验。今天&#xff0c;我们要探讨的正是这样一…

C++ | Leetcode C++题解之第384题打乱数组

题目&#xff1a; 题解&#xff1a; class Solution { public:Solution(vector<int>& nums) {this->nums nums;this->original.resize(nums.size());copy(nums.begin(), nums.end(), original.begin());}vector<int> reset() {copy(original.begin(), …

重新修改 Qt 项目的 Kit 配置

要重新修改 Qt 项目的 Kit 配置&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1. 打开 Qt Creator 首先&#xff0c;启动 Qt Creator&#xff0c;确保你的项目已经打开。 2. 进入项目设置 在 Qt Creator 中&#xff0c;点击菜单栏的 “Projects” 标签&#xff08;通…

Java并发编程面试必备:如何创建线程池、线程池拒绝策略

一、线程池 1. 线程池使用 1.1 如何配置线程池大小 如何配置线程池大小要看业务系统执行的任务更多的是计算密集型任务&#xff0c;还是I/O密集型任务。大家可以从这两个方面来回答面试官。 &#xff08;1&#xff09;如果是计算密集型任务&#xff0c;通常情况下&#xff…

中仕公考怎么样?公务员考试什么时候补录?

公务员考试补录的时间和方法通常因地区和职位的不同有所区别&#xff0c;一般来说&#xff0c;这一过程会在面试、体检和考核环节完成后启动。 如果在招录过程中出现职位空缺或者并未全部招满的情况&#xff0c;就会进行补录。用人单位会通过其官方或公告形式公布相关信息&…

关于武汉芯景科技有限公司的实时时钟芯片XJ8337开发指南(兼容DS1337)

一、芯片引脚介绍 1.芯片引脚 2.引脚描述 二、系统结构图 三、功能描述 1.时钟功能 2.闹钟功能&#xff08;两个闹钟&#xff09; 3.振荡器停止控制 4.频率输出&#xff08;1HZ、1.096KHZ、8.192KHZ、32.768KHZ&#xff09; 5.振荡器停止检测 6.闹钟中断标志 四、程序代…

短时傅里叶变换(Short-Time Fourier Transform, STFT),语音识别

高能预警&#xff01;&#xff01;&#xff01; .wav文件为笔者亲自一展歌喉录制的噪声&#xff0c;在家中播放&#xff0c;可驱赶耗子&#xff0c;蟑螂 介绍 短时傅里叶变换&#xff08;Short-Time Fourier Transform, STFT&#xff09;是一种时频分析方法&#xff0c;用于…

如何在银河麒麟操作系统中为文件加锁与解锁

如何在银河麒麟操作系统中为文件加锁与解锁 1、加锁2、解锁 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、加锁 若要为文件加锁&#xff0c;防止被修改或删除&#xff0c;可以使用chattr命令并加上i选项。这需要root权限。 命令&…

华为OD机试真题 - 多段数据压缩(Java/Python/JS/C/C++ 2024 D卷 100分)

华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试真题(Java/Python/JS/C/C++)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX…

网络基础+Socket

目录 下图为数据分用的过程 认识IP地址 认识MAC地址 认识端口号 网络字节序 sockaddr结构 Makefile新写法 下图为数据分用的过程 认识IP地址 IP协议有两个版本, IPv4和IPv6. 我们整个的课程, 凡是提到IP协议, 没有特殊说明的, 默认都是指IPv4 IP地址是在IP协议中, 用来…

js实现3d拖拽环绕旋转

js实现拖动节点围绕圆心转动 1.使用transform属性&#xff0c;将圆环放倒展示为椭圆 圆环上有不同的色彩&#xff0c;在转动的同时&#xff0c;需要让圆环也转动&#xff0c;所以圆环不能是椭圆&#xff0c;而是圆形&#xff0c;这样在转动的时候&#xff0c;改变rotate&…

开源 AI 智能名片 S2B2C 商城小程序在现代商业中的创新与启示

摘要&#xff1a;本文通过分析一种以 9.9 元裙子为代表的独特商业模式&#xff0c;探讨了其背后的现金流、产品和渠道组合策略&#xff0c;以及开源 AI 智能名片 S2B2C 商城小程序在其中可能发挥的作用和带来的启示。 一、引言 在当今竞争激烈的商业环境中&#xff0c;企业不断…

认知杂谈41

今天分享 有人说的一段争议性的话 I I 贫富根源在观念 I 你知道不&#xff1f;穷人穷啊&#xff0c;好多时候是因为他们自己还有家里好几代人呢&#xff0c;都陷在一种不对的想法里出不来&#xff0c;还觉得这样挺好&#xff0c;就一直这么过下去了。可富人的那些想法呢&am…