大话C语言:第46篇 C语言项目工程化之Makefile详解

1 Makefile概述

Makefile是一种用于自动化构建和管理程序的工具,以文本文件的形式存在。它主要记录了程序的编译规则、依赖关系和操作指令,使得在开发过程中能够轻松地进行代码的编译、链接和部署。

Makefile文件中的命令有一定规范,一旦该文件编写好以后在Linux命令行中执行一条make命令即可自动编译整个工程。不同厂家的make可能会稍有不同,并且语法上也有区别,不过基本思想都差不多,主要还是落在目标依赖上,最广泛使用的是GNUmake。

Makefile的主要作用包括:

  • 自动化编译和链接:通过定义编译器、编译选项、源文件、目标文件等信息,Makefile可以自动完成代码的编译和链接过程,从而提高开发效率。

  • 管理依赖关系:Makefile中可以指定各个源文件之间的依赖关系,确保在修改某个文件后,只需要重新编译该文件及其相关的依赖文件。

  • 跳过不必要的编译:Makefile通过检查源文件和目标文件的时间戳,可以避免重新编译未发生变化的文件,从而加快构建速度。

2 make概述

make是一个命令行工具和构建自动化工具,主要用于管理和构建软件项目。它通过Makefile文件来定义构建任务和依赖关系,并根据这些规则自动执行任务以生成最终的目标文件。

Makefile中包含了目标、依赖关系和相应的构建命令。make工具会根据目标和依赖关系来确定构建顺序,并执行构建命令以生成新的目标文件。这使得make能够根据代码的依赖关系自动构建并更新目标文件,从而提高编译效率。

make具有依赖管理功能,可以确保任务的正确构建顺序。它支持任务并行执行,并可以在多种操作系统上运行。make是一个稳定且广泛使用的工具,已经存在多年,适用于多种编程语言和项目类型的构建任务。

3 为什么需要make和Makefile?

之所以需要make和Makefile,主要原因主要可以归结为以下几点:

  • 自动化编译:在软件开发过程中,尤其是大型项目,源文件众多,编译过程复杂。手动编译不仅效率低下,而且容易出错。make和Makefile可以自动化地处理编译过程,根据源文件的依赖关系自动确定编译顺序,并执行相应的编译命令,大大提高了编译效率。

  • 管理依赖关系:在软件项目中,源文件之间往往存在复杂的依赖关系。当一个源文件发生变化时,可能需要重新编译与其相关的其他源文件。make和Makefile可以方便地管理这些依赖关系,确保在需要时只重新编译必要的文件,避免了不必要的重复编译。

  • 抽象构建逻辑:Makefile将构建逻辑抽象化,使得开发者能够专注于代码编写,而不必关心繁琐的构建过程。Makefile中定义了目标、依赖和命令,使得构建过程更加清晰、易于理解和维护。

  • 跨平台兼容性:make工具在不同的操作系统上都可以使用,只需要根据不同的操作系统和编译器编写相应的Makefile即可。这使得软件项目可以在不同的平台上进行构建,提高了项目的可移植性。

  • 可定制性:Makefile具有很高的可定制性,可以根据项目的具体需求进行灵活的配置。开发者可以定义自己的变量、规则和函数,以满足特定的构建需求。

  • 提高开发效率:通过自动化编译和管理依赖关系,make和Makefile可以大大减少开发人员在构建过程中的重复劳动,让他们有更多的时间专注于代码的开发和调试,从而提高开发效率。

4 Makefile语法规则

4.1 模块代码

实现简单计算器功能,核心功能包括:

  • 两位数的加法,功能文件add.h和add.c

  • 两位数的加法,功能文件subtract.h和subtract.c

  • 两位数的除法,功能文件divide.h和divide.c

  • 两位数的乘法,功能文件multiply.h和multiply.c

  • 计算器功能主入口,功能文件calculator.c

核心功能模块:两位数的加法

// add.h文件实现
#ifndef __ADD_H__
#define __ADD_H__/*
* 功能:实现两个整数相加
* @param num1 : 被加数
* @param num2 : 加数
* @return 两数相加之和
* @note 不考虑溢出问题
*/
int add(int num1, int num2); #endif
// add.c文件实现
#include "add.h"int add(int num1, int num2) 
{  return num1 + num2;  
}

核心功能模块:两位数的减法

// subtract.h文件实现
#ifndef __SUBTRACT_H__
#define __SUBTRACT_H__/*
* 功能:实现两个整数相减
* @param num1 : 被减数
* @param num2 : 减数
* @return 两数相减之差
* @note 不考虑溢出问题
*/
int subtract(int num1, int num2);  #endif
// subtract.c文件实现
#include "subtract.h"  int subtract(int num1, int num2) 
{  return num1 - num2;  
}

核心功能模块:两位数的除法

// divide.h文件实现
#ifndef __DIVIDE_H__
#define __DIVIDE_H__/*
* 功能:实现两个整数相除
* @param num1 : 被除数
* @param num2 : 除数,不能为0
* @return 两数相除
* @note 除数不能为0
*/
double divide(int num1, int num2); #endif
// divide.C文件实现
#include "divide.h" double divide(int num1, int num2) 
{  if (b != 0) {  return (double)num1 / num2;  } else {  return 0.0;  }  
}

核心功能模块:两位数的乘法

// multiply.h文件实现
#ifndef __MULTIPLY_H__
#define __MULTIPLY_H__/*
* 功能:实现两个整数相乘
* @param num1 : 被乘数
* @param num2 : 乘数
* @return 两数相乘
* @note 不考虑溢出问题
*/
int multiply(int num1, int num2);  #endif
// multiply.c文件实现
#include "multiply.h"  int multiply(int num1, int num2) 
{  return num1 * num2;  
}

核心功能模块:计算器功能主入口法

// calculator.c文件实现
#include <stdio.h>  
#include "add.h"
#include "subtract.h"  
#include "divide.h" 
#include "multiply.h" int main() 
{  int num1 = 20int num2 = 5;  printf("两数相加之和: %d\n", add(num1, num2));  printf("两数相减之差: %d\n", subtract(num1, num2));  printf("两数相乘之积: %d\n", multiply(num1, num2));  printf("两数相除之商: %f\n", divide(num1, num2));  return 0;  
}

4.2 Makefile基础语法规则

Makefile遵循基础规则如下:

target: dependencies  command  ...

其中,

- `target`:表示要生成的目标文件或执行的动作,它通常是文件名或特殊的伪目标(如`clean`)。
- `dependencies`:表示生成目标所依赖的文件列表,多个文件之间使用空格分隔。
- `command`:表示生成目标时执行的命令,每个命令占一行,且前面必须有一个Tab字符作为缩进。

4.3 make命令格式

make命令格式如下:

make [选项] [目标]

其中,

  • 选项:是 make 命令的可选参数,用于控制 make 的行为。

  • 目标:是要构建的目标文件或标签名。如果没有指定目标,make 会使用 Makefile 中的第一个目标(通常是 all

make 命令选项包括:

  • -f FILE--file=FILE:使用指定的 Makefile,而不是默认的 Makefilemakefile

  • -n--just-print:显示将要执行的命令,但并不真正执行它们。

  • -s--silent--quiet:不显示执行的命令。

  • -k--keep-going:在出现错误时继续执行其他命令。

  • -j [N]--jobs[=N]:同时运行命令的个数。如果没有指定 N,则 make 会尝试并行执行尽可能多的任务。

  • --load-average[=LOAD]:当系统负载高于某个值时,不运行新的任务。

  • -C DIR--directory=DIR:在读取 Makefile 之前改变到指定的目录 DIR

目标 通常是 Makefile 中定义的一个标签或文件名,它可能代表一个要构建的特定目标文件,或者是一组任务的集合。例如,Makefile 中可能定义了 allcleaninstall 等目标。

一般的时候,直接使用make就可以。

4.4 多文件Makefile设计

以简单计算器为例,设计一个多文件编译的Makefile,具体内容如下:

calculator:calculator.o add.o subtract.o divide.o multiply.ogcc calculator.o add.o subtract.o divide.o multiply.o ‐o calculatorcalculator.o:calculator.cgcc ‐c calculator.c ‐o calculator.oadd.o:add.cgcc ‐c add.c ‐o add.osubtract.o:subtract.cgcc ‐c subtract.c ‐o subtract.odivide.o:divide.cgcc ‐c divide.c ‐o divide.omultiply.o:multiply.cgcc ‐c multiply.c ‐o multiply.oclean:rm *.o calculator ‐rf

注意,运行时使用make clean 就会执行clean后面的命令

5 Makefile变量

Makefile支持变量的使用,类似于C语言中的宏定义。变量可以用于存储文件名、编译选项等,使得Makefile更易于维护。

Makefile中的变量可以根据其来源和用途分为以下几类:

  • 环境变量:执行Makefile时从外部传入Make命令的环境变量。例如,`PWD 表示当前目录的绝对路径。环境变量通常是全局的,并且可以在整个Makefile中引用。

  • 内置变量或默认变量:Makefile中有一些内置的或默认的变量,这些变量在Makefile中有特定的含义和用途。例如,CC 用于指定编译器的类型,CFLAGS 用于指定编译器选项(如调试信息、优化等)。这些变量在Makefile中预定义,并且可以在需要时进行修改。

  • 自动变量:自动变量是Makefile中根据当前的目标和依赖关系自动生成的变量。例如,$@ 表示目标文件的名称(包含扩展名),$< 表示第一个依赖文件的名称。这些变量在规则中特别有用,因为它们可以根据上下文自动变化。

  • 用户自定义变量:除了上述几类变量外,用户还可以在Makefile中定义自己的变量。这些变量可以根据需要命名和赋值,用于存储文件名、目录路径、编译器选项等常用的定义,并可以动态地用于自动构建步骤的指令中。

5.1 用户自定义变量

用户自定义变量规则:

# 定义变量:
变量名=变量值# 变量引用或者使用
$(变量名)或${变量名}

注意:

  • 变量是大小写敏感的

  • 变量一般都在makefile的头部定义

  • 变量几乎可在makefile的任何地方使用

5.2 内置变量

makefile中有许多预定义变量,这些变量具有特殊的含义,可在makefile中直接使用。

变量名含义
$@当前规则中的目标文件名
$<当前规则中的第一个依赖文件名
$^当前规则中的所有依赖文件名列表
AR归档维护程序的程序名,默认值为ar
ARFLAGS归档维护程序的选项
AS汇编程序的名称,默认值为as
ASFLAGS汇编程序的选项
CCC编译器的名称,默认值为cc
CFLAGSC编译器的选项
CPPC预编译器的名称,默认值为$(CC) -E
CPPFLAGSC预编译的选项
CXXC++编译器的名称,默认值为g++
CXXFLAGSC++编译器的选项

5.3 优化多文件Makefile

基于Makefile变量规则,进一步优化设计多文件Makefile,优化后的Makefile如下:

# 编译器选项
CC=gcc
CFLAGS=‐Wall ‐g#目标文件选项
obj=calculator
obj1=add
obj2=subtract
obj3=divide
obj4=multiply
OBJ=calculator.o add.o subtract.o divide.o multiply.o# 构建目标程序
$(obj):$(OBJ)$(CC) $^ ‐o $@# 编译目标文件
$(obj).o:$(obj).c$(CC) $(CFLAGS) ‐c $< ‐o $@$(obj1).o:$(obj1).c$(CC) $(CFLAGS) ‐c $< ‐o $@$(obj2).o:$(obj2).c$(CC) $(CFLAGS) ‐c $< ‐o $@$(obj3).o:$(obj3).c$(CC) $(CFLAGS) ‐c $< ‐o $@$(obj4).o:$(obj4).c$(CC) $(CFLAGS) ‐c $< ‐o $@	# 中间文件清理
clean:rm $(OBJ) $(obj) ‐rf

Makefile支持使用通配符来匹配文件名,常用的通配符有:%:代表任意数量的任意字符,可以用于匹配文件名中的一部分。例如,%.o 表示所有以 .o 结尾的文件。

进一步优化Makefile文件:

# 编译器选项
CC=gcc
CFLAGS=‐Wall ‐g#目标文件选项
obj=calculator
OBJ=calculator.o add.o subtract.o divide.o multiply.o$(obj):$(OBJ)$(CC) $^ ‐o $@%.o:%.c$(CC) $(CFLAGS) ‐c $< ‐o $@# 中间文件清理
clean:
rm $(OBJ) $(obj) ‐rf

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

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

相关文章

Unity--XLua调用C#

Unity–XLua调用C# 由于Unity/C# 和lua是两种语言&#xff0c;两种语言的特性不一样&#xff0c;因此&#xff0c;如果要互相调用的话&#xff0c;需要第三方作桥梁. 因此&#xff0c;为了在Unity中/C#中使用lua的特性&#xff0c;需要在Unity中安装插件&#xff0c;Xlua/toLu…

【学习笔记】8、脉冲波形的变换与产生

本章简略记录。 8.1 单稳态触发器&#xff08;脉冲触发&#xff09; 单稳态触发器 应用于 &#xff1a;&#xff08;1&#xff09;脉冲整型&#xff08;2&#xff09;脉冲延时 &#xff08;3&#xff09;定时 单稳态触发器的工作特性&#xff1a; 没有触发脉冲作用时&#xf…

Flink入门(五)--Flink算子

Map DataStream → DataStream 一个接受一个元素并产生一个元素的函数。 示例 dataStream.map { x > x * 2 } FlatMap DataStream → DataStream 一个接受一个元素并产生零个、一个或多个元素的函数。 例如 dataStream.flatMap { str > str.split(" ") }…

besier打断和升阶,高阶性质

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 问题描述 对besier曲线在u处打断&#xff0c;生成两条besier曲线对besier曲线升阶处理 bezier高阶性质 求导推导 P ( t ) ∑ i 0 n B i n ( t ) b i \boldsymbol …

Python 爬虫入门(十二):正则表达式「详细介绍」

Python 爬虫入门&#xff08;十二&#xff09;&#xff1a;正则表达式 前言一、正则表达式的用途二、正则表达式的基本组成元素2.1 特殊字符2.2 量词2.3 位置锚点2.4 断言2.5 字符集2.6 字符类2.6.1 基本字符类2.6.2 常见字符类简写2.6.3 POSIX字符类2.6.4 组合使用 三、 正则表…

Datawhale X 李宏毅苹果书 AI夏令营 学习笔记(二)

自适应学习率 我们梯度下降在参数更新上&#xff0c;公式是 W t W t − 1 − η g t &#xff0c; η 是学习率&#xff0c; g t 是梯度 W_tW_{t-1}-\eta g_t&#xff0c;\eta是学习率&#xff0c;g_t是梯度 Wt​Wt−1​−ηgt​&#xff0c;η是学习率&#xff0c;gt​是梯度…

03_React 收集表单数据和 组件生命周期

React 收集表单数据和 组件生命周期 一、收集表单数据1、例子1.1 需求&#xff1a;定义一个包含表单的组件&#xff0c;输入用户名密码后&#xff0c;点击登录提示输入信息 2、理解&#xff1a;包含表单的组件分类2.1 受控组件2.2 非受控组件 二、高阶函数\_函数柯里化1、复习-…

9 正则表达式:Java爬虫和正则表达式、String中的正则表达式方法(基本语法7)

文章目录 前言一、正则表达式1 [ ] 语法(1)[ABC] 和 [^ABC](2)[A-Z]和[a-zA-Z]小总结2 特殊字符语法(\w 这些)3 数量符4 \ 、()、 |5 锚点 ^ 和 $,\b,\B6 (?i) : 忽略其后面的大小写 ---- 这个Java是可以的,其他语言我不知道(正则表达式虽然大多通用,但也有部分是…

zabbix5.0与7.0版本区别 切换建议

Zabbix5.0和Zabbix7.0的区别 1. 性能和扩展性优化 1.1 高效的数据处理和存储 优化的数据库性能&#xff1a; Zabbix 7.0 在数据库层面进行了多项优化&#xff0c;以减少查询延迟和提高数据处理速度。这包括对数据库结构的改进和索引优化&#xff0c;使得大规模数据的读取和写…

Spark-driver和executor启动过程

一、上下文 《Spark-SparkSubmit详细过程》详细分析了从脚本提交任务后driver是如何调用到自己编写的Spark代码的&#xff0c;而我们的Spark代码在运行前必须准备好分布式资源&#xff0c;接下来我们就分析下资源是如何分配的 二、Spark代码示例 我们以一个简单的WordCount程…

打卡学习Python爬虫第五天|Xpath解析的使用

什么是Xpath&#xff1f;是在XML文档中搜索内容的一门语言&#xff0c;HTML可以看作是xml的一个子集。 目录 1、安装lxml模块 2、导入lxml中的etree子模块 3、Xpath使用方法 3.1.选择节点 3.2.选择属性 3.3.选择文本内容 3.4.使用通配符*过滤节点 3.5.使用中括号[]索引…

Java学习_20_File以及IO流

文章目录 前言一、FileFile中常见的成员方法判断和获取创建和删除获取和遍历 二、IO流IO流体系结构字节流字节输出流&#xff1a;FileOutputStream字节输入流FileInputStrea文件拷贝try……catch异常处理中文乱码现象 字符流字符流读取FileReader字符流输出FileWriter底层原理 …

Linux 命令集合

1. linux 系统版本 1.1 linux系统的分类 linux系统&#xff0c;主要分Debian系和RedHat系&#xff0c;还有其它自由的发布版本。 1、Debian系主要有Debian&#xff0c;Ubuntu&#xff0c;Mint等及其衍生版本&#xff1b; 2、RedHat系主要有RedHat&#xff0c;Fedora&#xf…

Springsecurity 自定义AuthenticationManager

一、认证流程 1、当用户提交了一个他的凭证(用户名、密码) AbstractAuthenticationProcessingFilter 将会创建一个凭证信息&#xff0c;最终&#xff0c;该请求会被UsernamePasswordAuthenticationFilter 拦截将请求中用户名和密码&#xff0c;封装为 Authentication 对象&…

C++ | Leetcode C++题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; class Solution { public:bool canMeasureWater(int x, int y, int z) {if (x y < z) {return false;}if (x 0 || y 0) {return z 0 || x y z;}return z % gcd(x, y) 0;} };

AI大模型进化之路:机器学习九大算法画图详解

机器学习算法对于了解AI大模型的意义非常重要&#xff0c;它们是构建、训练和应用AI大模型的基础和关键。今天给大家整理了一份机器学习核心算法资料&#xff0c;建议收藏学习。 集成学习是一种机器学习算法&#xff0c;它通过构建多个模型并整合它们的预测结果来提高性能。常…

ST 表算法

ST 表 ST 表&#xff0c;主要思想是空间换时间&#xff0c;用于解决可重复贡献问题和 RMQ 问题。 可重复贡献问题 指某个运算 o p op op&#xff0c;有 x o p x x x\ op\ x\ \ x x op x x 。例如 m a x ( x , x ) x m i n ( x , x ) x g c d ( x , x ) x max(x,x)x\…

【Docker】Docker学习01 | 什么是docker?

本文首发于 ❄️慕雪的寒舍 因为本人没有学习过docker&#xff0c;虽然部署过很多镜像&#xff0c;但是对于docker底层的实现一概不知。趁学习一个新项目的契机&#xff0c;将docker的相关概念了解清楚。 安装docker的教程请查看 Linux主机安装docker。 如果你想和我一起学习do…

TCP的连接建立及报文段首部格式

粘包问题&#xff1a; 原因&#xff1a;TCP流式套接字&#xff1b;数据与数据之间没有边界&#xff1b;导致可能多次的数据粘到一起。 解决办法&#xff1a; 规定一些数据与数据之间的间隔符&#xff0c;如&#xff1a;"\aa\", "\r\n"。指定要发送的数据…

exec函数簇

一、main 函数的参数定义 在C语言中&#xff0c;main 函数是程序执行的入口点。main 函数可以接受参数&#xff0c;这些参数通常用于从命令行接收输入。main 函数的参数定义通常遵循以下形式&#xff1a; int main(int argc, char *argv[]) 或者等价地&#xff1a; int mai…