Logo

郎哥编程

GNU Make

2024-05-17 25

本文讲述的内容仅是GNU Make初步入门,对不熟悉GNU Make工具的C语言开发者起到一个引导作用。开发者通过阅读本文内容,可以编写简单的makefile脚本文件,并使用GNU Make在linux系统中自动构建C程序。若开发者需要进一步了解和学习GNU Make,请参见《GNU make 中文手册》。

为什么要使用GNU Make

GNU Make(简称Make)是一个项目构建工具,能够方便地编译、链接多个C源代码文件,自动决定哪些C源文件需要重新编译,从而高效地构建C程序。

在Windows系统中,开发者可以使用Microsoft Visual Studio集成工具来开发和管理大型的C程序项目,但Visual Studio编译的C程序只能在Windows上运行,并不兼容Linux等其它操作系统。当开发者需要开发在Linux操作系统运行的C程序时,就要用到Make构建工具。

另外,Make构建工具在多种操作系统上可用,包括Linux、Windows和macOS等,它允许开发者能够在不同的平台上使用相同的构建系统。

一个使用Make的构建案例

在《C程序的结构》一节提供了面积计算器程序示例,面积计算器程序共有五个源文件,主源文件为main.c,计算长方形面积的源文件为rectangle.c,计算正方形面积的源文件为square.c,计算平行四边形面积的源文件为paraller.c,计算三角形面积的源文件为triangle.c。

 

我们使用Make工具在Linux系统下自动化编译和链接这些文件,生成一个可执行文件。

使用Make工具构建C程序,需要创建一个Makefile文件,该文件告诉Make如何构建C程序。

在项目根目录下创建一个名为 Makefile 的文件,注意没有扩展名,文件内容如下。

# Makefile
# 定义编译器
CC = gcc
# 定义编译选项
CFLAGS = -Wall -Werror
# 定义源文件
SRCS = main.c square.c triangle.c rectangle.c paraller.c
# 定义目标文件
OBJS = $(SRCS:.c=.o)
# 定义可执行文件
TARGET = area
# 默认目标
all: $(TARGET)
# 链接目标文件生成可执行文件
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
# 编译源文件生成目标文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@

从文件内容可以看出,Makefile描述了C程序所有文件的编译顺序、编译规则。编写Makefile文件,需要遵循Makefile的语法规则,在Makefile内可以使用系统shell所提供的任何命令。

在Makefile内,前缀为“#”字符的语句为注释语句,其它语句为可执行语句。Makefile内的CC、CFLAGS、SRCS、OBJS、TARGET为预定义的变量,变量名称前面添加符号“$”为变量引用,符号“$”的其它用法,后面会逐步介绍。all、clean为Makefile预定义的指令。

CC:定义编译器为 gcc。

CFLAGS:定义编译选项,包括 -Wall(显示所有警告)和 -Werror(将警告视为错误)。

SRCS:定义所有源文件的列表。

OBJS:通过替换 .c 扩展名为 .o 来定义目标文件的列表。

TARGET:定义最终生成的可执行文件名。

all:默认目标,依赖于 $(TARGET),即最终的可执行文件。

$(TARGET): $(OBJS):规则说明如何从目标文件生成可执行文件。

%.o: %.c:模式规则,说明如何从源文件生成目标文件。

 

下面以Alibaba Cloud Linux操作系统为例,讲解在Linux操作系统下应用Make工具完成C程序的构建。面积计算机器程序文件构成如下图所示:

若当前使用的Linux操作系统没有安装GNU工具,需要先安装GNU工具。在当前Linux操作系统下建立存储C程序文件的目录,将C程序相关文件复制到该目录,将工作目录切换到该目录,执行下面的命令:

[root@iZ2zejfpm4bzceulyk5jw9Z area]# make

area是存储C程序相关文件的根目录,make会自动读取 makefile 文件,并执行相应的规则来构建C程序,若代码没有问题,会输出下面的信息:

gcc -Wall -Werror -c main.c -o main.o
gcc -Wall -Werror -c square.c -o square.o
gcc -Wall -Werror -c triangle.c -o triangle.o
gcc -Wall -Werror -c rectangle.c -o rectangle.o
gcc -Wall -Werror -c paraller.c -o paraller.o
gcc -Wall -Werror -o area main.o square.o triangle.o rectangle.o paraller.o

并在当前目录下输出可执行文件area。

程序模块化

Make工具主要用于多源代码文件的C程序项目,若整个C程序仅有一个源代码文件,没有必要使用Make工具,直接使用GCC编译器即可。

假如要编写一个面积计算器程序,用于帮助学生理解平面几何图形的边长与面积的关系,程序可以计算长方形、正方形、平行四边形、三角形的面积,开发者该如何设计这个程序呢?

最简单、最直接的设计方式是将所有代码放在一个源文件内,但这不是最好的程序设计方式,因为在设计程序时还需要考虑到程序的可阅读性、可维护性和可扩展性。

当一个C程序需要编写几千行、甚至上万行代码时,若把这些代码放置在一个源文件内,对代码的阅读、修改都会带来不便,对日后的程序维护和功能扩展也会增加难度和复杂度。

平时我们在解决一些复杂问题时,会把一个复杂的问题分解成多个子问题,先逐个解决这些子问题,当这些子问题解决后,复杂的问题自然就得到了解决。把复杂的问题分解成多个子问题就是分而治之的思想,分而治之的思想同样也可以使用到编程过程中。对于复杂的程序,可以对程序的功能进行分解,将程序的功能分解成多个子功能,从而达到将复杂问题进行简化的目的。

面积计算器可以分解为计算长方形的面积、计算正方形的面积、计算平行四边形的面积、计算三角形的面积四个子功能,每个子功能是一个独立的模块(一个模块可以对应一个或多个源文件)。

按照功能划分,面积计算器程序可划分为五个模块,主模块为main.c,计算长方形面积的模块为rectangle.c,计算正方形面积的模块为square.c,计算平行四边形面积的模块为paraller.c,计算三角形面积的模块为triangle.c。

Make变量

 makefile 中变量的命名可以使用字符、数字和下划线,但是要注意变量名对大小写是敏感的。

变量的定义:

变量 = 值1 值2 ……

变量的使用:

$(变量) 等效为:值1 值2 ……

Make已经在内部预定义了一部分变量,这些预定义的变量具有特定的含义。

CC:编译器的名称,默认值为:cc 开发者可以修改其默认值。例如:CC = gcc;

CFLAGS:定义编译选项,无默认值,开发者可以自己赋值。例如:CFLAGS = -Wall -Werror;

SRCS:指定源文件。例如:

SRCS = main.c square.c triangle.c rectangle.c paraller.c

若列出的源文件和makefile文件不在同一目录,源文件需要添加相对路径;

OBJS:定义目标文件。例如:

OBJS = $(SRCS:.c=.o)

值$(SRCS:.c=.o)通过替换 .c 扩展名为 .o 来定义目标文件的列表。

TARGET: 定义最终生成的C程序可执行文件。例如:

TARGET = area

面积计算器程序可执行文件名称为area。

……

更多预定义变量的使用请查阅GNU Make相关资料。

Make规则

makefile的文件内容是由多条规则构成的,make命令执行时先在makefile 文件中查找各种规则,对各种规则进行解析后运行规则。规则的基本格式如下:

## 规则的语法格式:
target1,target2...: depend1, depend2, ...
	command
	......
	......

规则由target(目标)、depend(依赖)、command(命令)构成。

例如,下面的规则用于链接目标文件并生成可执行文件:

$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^

规则第1行为目标和依赖。$(TARGET)是目标,该目标为TARGET变量的值;$(OBJS)为依赖,该依赖为OBJS变量的值。

规则第2行为命令,每条命令前必须有一个Tab缩进并且独占一行,命令一般是操作系统的Shell命令。

$(CC)为Shell命令,其值为gcc,即gcc编译器命令,在操作系统的Shell窗口输入gcc命令及其依赖参数,可以执行C程序的编译和链接任务。

$(CFLAGS) -o $@ $^为gcc命令依赖的参数。$(CFLAGS)让gcc打开警告信,并将警告作为错误处理;-o指定输出项为文件;$@指向目标文件,即$(TARGET)指向的目标文件;$^指向所有的依赖文件,即$(OBJS)指向的依赖文件。

再如,下面的规则用于编译源文件并生成编译后的目标文件:

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

规则第1行为目标和依赖。%.o是目标,%是通配符,输出目标文件为.o文件;%.c为依赖,该依赖为所有的.c源文件。

规则第2行为命令,$(CC)为gcc命令;$(CFLAGS) -c $< -o $@为gcc命令依赖的参数,其中$<为第一个依赖文件,案例中第一个依赖文件是main.c,$@指向目标文件。

函数

Makefile中的函数是一些特殊的宏或命令,它们可以通过定义和调用来实现特定的功能。这些宏或命令可以在 Makefile 的不同部分中重复使用,以提高代码的可重用性和可维护性。

自定义函数

可以在 Makefile 中定义自定义函数,这些函数可以通过调用 $(命令名) 的方式在其他地方使用。这可以用于封装一些复杂的构建逻辑,提高代码的可读性和可维护性。

例如:

define compile_and_link
    $(CC) $(CFLAGS) -o $@ $^
endef
TARGET = myprogram
$(TARGET): main.o util.o
    $(call compile_and_link)

预定义的字符串处理函数

字符串处理函数可以执行各种字符串操作,如替换、截取、比较等。以下是一些常用的 Makefile 字符串函数及其用法:

$(subst FROM,TO,TEXT)

替换功能:在 TEXT 中,将 FROM 替换为 TO。

示例:$(subst foo,bar,$(VAR)) 如果 VAR 是 foobar,那么结果是 barbar。

$(patsubst PATTERN,REPLACEMENT,TEXT)

模式替换:查找 TEXT 中所有符合 PATTERN 的部分,并用 REPLACEMENT 替换它们。

示例:$(patsubst %.c,%.o,$(FILES)) 如果 FILES 是 main.c util.c,那么结果是 main.o util.o。

$(strip STRING)

去除空格:去除 STRING 两侧的空白字符(包括空格、制表符和换行符)。

示例:$(strip foo bar baz) 结果是 foo bar baz。

$(strip STRING)

去除空格:去除 STRING 两侧的空白字符(包括空格、制表符和换行符)。

示例:$(strip foo bar baz) 结果是 foo bar baz。

$(findstring FIND,IN)

查找子串:如果 FIND 出现在 IN 中,返回 FIND;否则返回空。

示例:$(findstring foo,foobar) 结果是 foo。

$(filter PATTERN...,TEXT)

过滤:返回 TEXT 中所有符合 PATTERN 的单词。

示例:$(filter %.c %.h,$(FILES)) 如果 FILES 是 main.c util.c foo.h,那么结果是 main.c util.c foo.h。

代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论