[Makefile] 第二章:简单编写一个makefile

Makefile学习:第一章 GCC的简易用法-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_52980547/article/details/140468062?spm=1001.2014.3001.5502

前言:

        为什么要写一个makefile呢?这是因为当我们有多个文件需要编译链接的时候,如果一个一个文件去编译链接,那么流程就太多繁琐了,还有可能会出错,因此我们需要一个文件自动替我们去编译链接我们所写好的代码。

        那为什么会是makefile呢?是不是可以写一个shell脚本将所有编译链接的操作写在一起,也会是一样的效果?之所以我们用makefile而不是shell脚本来编译代码,原因如下:

  1. 依赖管理:Makefile的核心功能之一是自动处理依赖关系。它可以根据源文件的修改时间判断哪些文件需要重新编译,而哪些不需要。这意味着如果只修改了某个源文件,Make只会重新编译那个文件及其直接依赖的文件,而不是整个项目,极大地提高了编译效率。

  2. 模块化和重用性:Makefile支持规则的定义和复用,使得大型项目的构建过程可以模块化。你可以定义通用的构建规则,然后在不同的目标中重用这些规则,减少重复代码并易于维护。

  3. 简洁的语法:Makefile通过简洁的规则定义,使得构建过程的逻辑易于理解和编写。每个目标(如编译的可执行文件或库)及其依赖关系都可以清晰地表达。

  4. 跨平台性:虽然Makefile本身是平台无关的,但通过适当的编写,它可以适应不同操作系统上的编译环境。通过定义不同的规则针对不同平台,可以在多种环境下复用同一份Makefile。

  5. 标准化:Make是一个广泛接受的构建工具,特别是在C/C++项目中,几乎成为了事实上的标准。开发者社区对Makefile的结构和约定有较深的理解,便于协作和维护。

        相比之下,虽然Shell脚本也能执行编译命令,但它缺乏自动依赖管理和高效的增量构建机制。在Shell脚本中,你需要手动检查文件修改时间和执行相应的编译命令,这在大型项目中会变得非常低效和复杂。

一、简单编写一个makefile

        让我们通过一个简单的C语言项目来说明Makefile的工作原理。假设我们有一个项目,包含两个源文件:hello.c,thanks.c (假设还有头文件 hello.h,thanks.h,依旧使用下面代码)。我们的目标是编译这些源文件并链接生成一个名为 my_program 的可执行文件。(如果我们有多的.c文件在SOURCES 地方继续添加即可),在控制台中输入nano makefile,进入后编辑。

1.1 makefile代码

# 定义编译器CC为gcc
# 编译选项CFLAGS包含了警告所有告警-Wall和调试信息-g
CC = gcc  
CFLAGS = -Wall -g

# 目标执行文件名
TARGET = my_program

# 源文件列表
SOURCES = hello.c thanks.c 
# 对应的目标文件列表,自动从源文件名生成
OBJECTS = $(SOURCES:.c=.o)

# 默认目标,即执行make时默认要做的事情
# 通过all标签定义了默认执行的任务,即编译所有源文件并链接生成可执行文件my_program
all: $(TARGET)

# 可执行文件的生成规则
# $(TARGET): $(OBJECTS) 表示my_program依赖于所有.o文件
# 接下来的命令行使用gcc编译器,结合编译选项和目标文件,生成最终的可执行文件。
# -lm 表示链接数学库(Math Library),说明见第一章
$(TARGET): $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) -o $(TARGET) -lm  

# .c文件到.o文件的编译规则
# %.o: %.c 是一个模式规则,说明任何.c文件如何被编译成相应的.o文件。这里$<代表依赖文件列表中的第 
# 一个文件(即.c文件),$@代表目标文件(即.o文件)
%.o: %.c %.h
	$(CC) $(CFLAGS) -c $< -o $@

# 清理规则,删除所有生成的.o文件和可执行文件,帮助保持项目目录干净
clean:
	rm -f $(OBJECTS) $(TARGET)

# 帮助信息,help目标提供了Makefile的使用指南,让用户了解如何使用这个Makefile
help:
	@echo "Makefile Usage:"
	@echo "  make all     - 编译所有源文件并链接生成可执行文件"
	@echo "  make clean   - 删除所有中间文件和可执行文件"

1.2 效果图

1.3 无注释版代码 

CC = gcc  
CFLAGS = -Wall -g

TARGET = my_program
SOURCES = hello.c thanks.c 
OBJECTS = $(SOURCES:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) -o $(TARGET) -lm  

%.o: %.c %.h
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJECTS) $(TARGET)

help:
	@echo "Makefile Usage:"
	@echo "  make all     - 编译所有源文件并链接生成可执行文件"
	@echo "  make clean   - 删除所有中间文件和可执行文件"

二、常用的一些概念

2.1 目标(Target)

        目标是 Makefile 中想要构建或更新的产物,它可以是一个可执行文件、静态库、动态库,甚至是其他任意文件。也可以是一个虚目标(phony target),用于代表一组操作,如clean每个目标定义了一组依赖关系和构建该目标所需的命令。例如,一个简单的可执行文件目标可能是这样的:

program: main.o utils.o

        这里program就是目标文件,同时program也是可执行文件,main.o utils.o就是依赖文件。

2.2 依赖(Dependencies)

        依赖是指目标在被创建或更新之前需要存在的文件或目标,列在冒号后面的是目标依赖的文件或目标。在 Makefile 中,目标之后列出的所有文件被视为其直接依赖。当依赖文件比目标文件新(修改时间更晚),或者依赖文件不存在时,Make会执行与目标关联的命令来更新目标。

main.o: main.c
func.o: func.c

2.3 命令(Commands)

        命令是 Makefile 中用来描述如何构建目标的操作序列,命令前需要以\开头或用tab键缩进,并且每行命令需要以;结尾(虽然在新版本的make中通常可省略)。例如,program: main.o utils.o\n\tgcc -o program main.o utils.o 中的 \t 表示制表符,紧接着的是编译命令。

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

func.o: func.c
        gcc -c func.c -o func.o

2.4 变量(Variables)

        变量是用来存储可重复使用的值的容器。在Makefile中,变量可以用来简化命令,使得在多个地方使用的相同值只需更改一处即可,如编译器路径、编译选项等。

        在Makefile中,$是一个特殊的字符,用于引用Makefile中定义的变量。当你看到$(variable)这样的表达式时,这意味着Make将会用变量variable的值来替换这个表达式。因此,$(CC)就是在引用一个名为CC的变量的值,即$(CC) = gcc

CC = gcc
CFLAGS = -Wall

main.o: main.c
        $(CC) $(CFLAGS) -c main.c -o main.o

func.o: func.c
        $(CC) $(CFLAGS) -c func.c -o func.o

        这里,CC变量存储了编译器的名称,而CFLAGS存储了编译时的选项。在命令行中使用$(CC)$(CFLAGS)来引用这些变量的值,这样如果需要改变编译器或编译选项,只需要修改变量定义即可。

2.5 模式规则(Pattern Rules)

        模式规则允许你为一类具有相似构建方式的文件定义规则,而不是为每一个文件单独编写规则。模式规则使用百分号(%)作为通配符来匹配文件名中变化的部分。

%.o: %.c
2    $(CC) -c $(CFLAGS) $< -o $@

        这个模式规则说明任何.o文件都依赖于同名的.c文件。$<代表依赖文件列表中的第一个文件(在这里是.c文件),$@代表目标文件(.o文件)。当你运行Make时,如果需要编译任何一个.c文件,Make会自动使用这条规则。 

2.6隐含规则(Implicit Rules)

        Make自带了一系列预定义的规则,称为隐含规则,这些规则定义了常见文件类型(如.c.o)的默认构建方式。这意味着即使没有显式写出规则,Make也可能知道如何构建某些类型的文件。

        示例:无需显式写入规则,Make知道如何根据.c文件生成.o文件。但为了明确或覆盖默认行为,你可以自定义规则。

2.7 .PHONY 目标

        标记某些目标为“伪目标”,即它们不是实际的文件。有时,一个目标并不对应于实际的文件,而是一系列操作的集合。标记这样的目标为 .PHONY 可以确保即使存在同名文件,Make 也会执行相应的命令。例如,.PHONY: clean,其中 clean 目标用于清理构建过程中产生的临时文件。

.PHONY: clean

clean:
    rm -f *.o myprog

        在这个例子中,clean是一个虚目标,用于删除所有的.o文件和可执行文件myprog。即使有一个名为clean的文件存在,执行make clean命令时,Make依然会执行删除操作,而不是检查clean文件是否需要更新。 

2.8自动变量

        在Makefile中,自动变量是在规则中自动设置的变量,它们的值取决于规则中的目标名和依赖文件名。这些变量使得规则更加通用和灵活。常见的自动变量包括:

  • $@:表示规则中的目标文件名。
  • $<:表示规则中的第一个依赖文件名。
  • $^:表示规则中所有依赖文件名的列表,忽略重复项。
  • $+:与$^类似,但保留重复的依赖项。
  • $(*)$(*.*)等:用于模式规则,分别匹配目标和依赖的模式部分。
objects = foo.o bar.o baz.o

foo: $(objects)
    gcc -o $@ $^

 这里,$@代表目标foo$^代表所有对象文件列表,即foo.o bar.o baz.o

2.9 include 指令

   include指令允许在Makefile中引入其他Makefile文件的内容。这有助于模块化和复用Makefile代码,特别是在大型项目中。

include config.mk

all: 
    $(MAKE) $(TARGETS)

2.10 条件表达式

        Make 支持基本的条件表达式,可以基于变量的值或文件的存在与否来决定是否执行某些部分的规则或设定不同的变量值。Makefile支持基本的条件表达式来实现逻辑判断,主要通过函数和特殊目标实现。常用的有ifeqifneqifdefifndef等。

ifdef DEBUG
CFLAGS += -g -O0
else
CFLAGS += -O2
endif

program: main.o utils.o
    $(CC) $(CFLAGS) -o $@ $^

        这段代码展示了根据是否定义了DEBUG变量来选择不同的编译优化选项。如果DEBUG被定义,则添加-g -O0选项进行调试;否则,使用-O2进行优化编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值