Makefile实验

news2024/11/24 11:52:50

一、准备C程序

1、input.h 

#ifndef __INPUT_H
#define __INPUT_H

void input_int(int *a, int *b);

#endif

2、input.c 

#include <stdio.h>
#include "input.h"

void input_int(int *a, int *b)
{
    printf("input two nums: ");
    scanf("%d %d", a, b);
    printf("\r\n");
}

3、calcu.h 

#ifndef __CALCU_H
#define __CALCU_H

int calcu(int a, int b);

#endif

4、calcu.c 

#include "calcu.h"

int calcu(int a, int b)
{
    return (a + b);
}

5、main.c 

#include <stdio.h>
#include "input.h"
#include "calcu.h"

int main()
{       
    int a, b, num;

    input_int(&a, &b);
    num = calcu(a, b);
    printf("%d + %d = %d\r\n", a, b, num);
    return 0;
}  

编译上述.c文件

gcc main.c calcu.c input.c -o main

执行生成的可执行文件main

./main

二、使用make工具和Makefile文件编译

有时候修改的文件过多,以至于想不起哪个文件被修改过,所以我们需要一个工具:
(1)如果工程没有编译过,那么工程中的所有.c文件都要被编译并且链接成可执行文件。
(2)如果工程中只有个别.c文件被修改了,那么只编译这些被修改的.c文件即可。
(3)如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的.c文件,并且链接成可执行文件。

故我们需要一个Makefile,在工程目录下创建名为"Makefile"的文件,文件名为"Makefile",注意区分大小写。

Makefile的格式

 注:该图来源于陈皓《跟我一起写Makefile》

 Makefile文件内容如下:

main: main.o input.o calcu.o
	gcc -o main main.o input.o calcu.o

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

input.o: input.c
	gcc -c input.c

calcu.o: calcu.c
	gcc -c calcu.c

clean:
	rm *.o
	rm main

 在所有 .c文件未被编译成 .o文件之前,我们可以通过make命令将所有的.c文件进行统一编译:

执行生成的可执行文件main

如果之后再对其中的某些程序进行修改,再次使用make命令,Makefile会帮助自动完成重新编译(根据最后修改日期是否变动,即使是多增加一个空格)

Makefile的规则

Makefile首先检查依赖文件是否都存在,若检查通过,则按照具体命令执行具体的规则。

以上图中的第一条规则为例,这条规则的目标是main,main.o、input.o和 calcu.o是生成main的依赖文件,如果要更新目标main,就必须要先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。

注意:命令列表中的每条命令必须以TAB键开始,不能使用空格!

make命令会为Makefile中的每个以TAB开始的命令创建一个Shell进程去执行。

首先更新第一条规则中的main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为Makefile的工作完成了。整个Makefile就是为了完成这个工作。在第一次编译的时候由于main还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o、input.o和 calcu.o这个三个.o文件,这三个.o文件目前还都没有,因此必须先更新这三个文件。make会查找以这三个.o文件为目标的规则并执行。(这可能就是老师上说的跟深度优先遍历有点像的原因吧hh~)

以main.o为例,发现更新main.o的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc -c main.c”,这行命令很熟悉了吧,就是生成main.o,其它两个.o文件同理。

最后一个规则目标是clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o文件以及可执行文件main,也就是说,clean的功能就是完成工程的清理,“make clean”的执行过程如下图所示。

总结一下make的执行过程:

(1)make命令会在当前目录下查找以Makefile(makefile其实也可以)命名的文件。
(2)当找到 Makefile文件以后就会按照 Makefile中定义的规则去编译生成最终的目标文件。

(3)当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比
目标文件晚)的话就会执行后面的命令来更新目标。

以上就是make的执行过程,make工具就是在 Makefile中一层一层的查找依赖关系,并执行相应的命令。编译出最终的可执行文件。Makefile的好处就是“自动化编译”,一旦写好了Makefile文件,以后只需要一个make命令即可完成整个工程的编译,极大的提高了开发效率。

Makefile的变量

跟C语言一样,Makefile 也支持变量。先看一下前面的例子:

上述Makefile语句中,main.o、input.o和 calcu.o这三个依赖文件,我们输入了两遍,当然了,我们的这个Makefile 比较小,还是可以接受手动输入的,但是如果 Makefile复杂的时候这种重复输入的工作就会非常费时间,而且非常容易输错,为了解决这个问题,Makefile加入了变量支持。不像C语言中的变量有int、char等各种类型,Makefile 中的变量都是字符串!类似于C语言中的宏。

使用变量将上面的代码修改,修改以后如下所示:

objects = main.o input.o calcu.o

main: $(objects)
	gcc -o main $(objects)

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

input.o: input.c
	gcc -c input.c

calcu.o: calcu.c
	gcc -c calcu.c

clean:
	rm *.o
	rm main

再执行一下更新后的Makefile文件(注意,此处笔者将Makefile文件名改为了makefile,以此证明小写字母也是可以的hh~) 

Makefile变量的赋值

(1)赋值符 "="

使用"="给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,例如:

name = qingshanke
curname = $(name)
name = cuizhuxuan
print:
	echo curname: $(curname)

使用 make print 指令执行结果如下: 

这说明 "=" 可以借助另一个变量,将变量的真实值推到后面去定义。

(2)赋值符 ":="

将上述例子中的 = 改为 :=,再次运行

name = qingshanke
curname := $(name)
name = cuizhuxuan
print:
	echo curname: $(curname)

使用 make print 指令执行结果如下:  

赋值符 ":=" 不会使用后面定义的变量,只能使用前面已经定义好的,这就是 = 和 := 二者的区别。

Makefile模式规则

main: main.o input.o calcu.o
	gcc -o main main.o input.o calcu.o
main.o: main.c
	gcc -c main.c
input.o: input.c
	gcc -c input.c
calcu.o: calcu.c
	gcc -c calcu.c

clean:
	rm *.o
	rm main

上述Makefile中第3~8行是将对应的.c源文件编译为.o文件,每一个C文件都要写一个对应的规则,如果工程中C文件很多的话显然不能这么做。为此,我们可以使用Makefile中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c文件编译为对应的.o文件。

模式规则中,至少在规则的目标定义中要包含“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有以.c结尾的文件,类似于通配符,a.%.c就表示以a.开头且以.c结束的所有文件。

当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方法如下:

%.o:%.c
    命令

注意:对于通配符而言,我们不能通过 -c %.c 来进行统一编译(如下图所示),因为-c后面需要准确的文件名,为此我们引入自动化变量。(正确的写法在后面会给出,莫慌hh~)

objects = main.o input.o calcu.o

main: $(objects)
	gcc -o main $(objects)

%.o:%.c
	gcc -c %.c

clean:
	rm *.o
	rm main

Makefile自动化变量

上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标呢?

自动化变量就是完成这个功能的。所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的逐个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如下图所示。

其中$@,$<和$^是我们最常用的三种。

因此,使用通配符就需要这样写

objects = main.o input.o calcu.o

main: $(objects)
	gcc -o main $(objects)

%.o:%.c
	gcc -c $<
	
clean:
	rm *.o
	rm main

Makefile伪目标

Makefile有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行make命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。

使用伪目标的主要是为了避免Makefile中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如在前面有如下代码用来完成清理工程的功能。

clean:
	rm *.o
	rm main

如果不小心在当前工作目录中创建了一个clean文件(通过ls命令我们可以看出该文件已经存在),那么make clean指令将不再有效。

 此时,我们可以通过指令.PHONY:clean 将clean文件设为伪目标,使得指令可以继续生效

objects = main.o input.o calcu.o

main: $(objects)
	gcc -o main $(objects)

%.o:%.c
	gcc -c $<

.PHONY:clean
clean:
	rm *.o
	rm main


本实验根据make工具和Makefile的使用_make makefile_受折磨的灵魂。的博客-CSDN博客

进行,相当于是对该博客实验的复现,非常感谢这位同学提供的实验例子,感谢感谢! 

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

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

相关文章

8年经验的面试官分享Android工程师的面试秘诀

本人目前在一家知名企业担任Android高级工程师&#xff0c;工作八年来面试不过不少Android工程师求职者&#xff0c;前后累积有两三百人。在本文内容中&#xff0c;小编将结合本人的面试经验&#xff0c;给各位正在找Android开发工作的小伙伴提出一些面试前的准备建议。 一&am…

vue3通用后台管理项目

一、创建项目 1、使用vite创建名为my-vue的项目&#xff1a;npm init vite-app my-vue 2、进入到my-vue文件夹下&#xff1a;cd my-vue 3、安装依赖包&#xff1a;npm install 4、运行该项目&#xff1a;npm run dev 二、引入element-plus 1、element-plus地址&#xff1a;http…

Camunda整体架构

REST API REST API 允许您从远程应用程序或 JavaScript 应用程序使用流程引擎。&#xff08;注意&#xff1a;REST API 的文档被分解为自己的文档。&#xff09;REST API Reference | docs.camunda.org Camunda Tasklist用于人工工作流管理和用户任务的 Web 应用程序&#xff0…

UniverSeg:通用医学图像分割模型来了!

自从今年以来ChatGPT爆火和GPT-4的发布&#xff0c;一时间在大模型的潮流下&#xff0c;通用人工智能&#xff08;AGI&#xff09;也呼之欲出。随着本月初SAM和SegGPT等通用的CV大模型的提出&#xff0c;大模型和通用模型这把火也逐渐烧到的CV领域&#xff0c;特别是图像分割领…

Linux应用编程(文件IO进阶)

一、Linux 系统如何管理文件 1.1、静态文件与 inode 文件存放在磁盘文件系统中&#xff0c;并且以一种固定的形式进行存放&#xff0c;我们把他们称为静态文件。 每一个文件都必须对应一个 inode&#xff0c;inode 实质上是一个结构体&#xff0c;这个结构体中有很多的元素&a…

【C语言】初阶指针(指针运算、二级指针及指针数组)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 指针运算 4.1 指针-整数 1.2 指针 - 指针 1.3 指针的关系运算 2. 指针和数组 3. 二级指针 4. 指针数组 1. 指针运算 指针-整数指针-指针指针的关系运算 4.1 指针-整数 上面这个程序的作用是将数组中每个元…

【攻城狮计划】Renesas RA2E1 开发板

&#x1f6a9;WRITE IN FRONT&#x1f6a9; &#x1f50e;介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四"&#x1f50e;&#x1f3c5;荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2022博客之星TOP10…

Scalable Recognition with a Vocabulary Tree(词汇树)

视觉单词 参考 视觉词袋&#xff08;BoVW&#xff0c;Bag of Visual Words&#xff09;模型&#xff0c;是“词袋”&#xff08;BoW&#xff0c;Bag of Words&#xff09;模型从自然语言处理与分析领域向图像处理与分析领域的一次自然推广。对于任意一幅图像&#xff0c;BoVW模…

jwt生成和解密-jose4j

jwt生成和解密-jose4j jwt的概念和生成意义在这里就不描述了&#xff0c;百度能搜到很多&#xff0c;直接上代码 官网地址 https://bitbucket.org/b_c/jose4j/wiki/Home maven <dependency><groupId>org.bitbucket.b_c</groupId><artifactId>jose4j…

【微信小程序】-- 配置uni-app的开发环境(四十八)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

Echarts图表显示不完全(多种图表解决方案)

前言 在使用Echarts画图的时候&#xff0c;有时候图表在固定大小的盒子模型&#xff08;dom容器&#xff09;中会显示不完全&#xff0c;因此我们需要对图表进行相关的调整使得图表内容显示完全。结合最近遇到的情况&#xff0c;提出一些解决方向 &#xff08;比较片面&#x…

Linux操作系统ARM体系结构处理器机制原理与实现

ARM 的概念ARM(Advanced RISC Machine)&#xff0c;既可以认为是一个公司的名字&#xff0c;也可以认为是对一类微处理器的通称&#xff0c;还可以认为是一种技术的名字。ARM 公司并不生产芯片也不销售芯片&#xff0c;它只出售芯片技术授权。其合作公司针对不同需求搭配各类硬…

【2023 · CANN训练营第一季】昇腾AI入门课(Pytorch)——第二章学习笔记

第二章 PyTorch模型迁移&调优 目标 了解 Pytorch 是如何适配到昇腾平台上的了解 Davinci 硬件架构以及什么样的模型在昇腾上更亲和了解软件术语和 Ascend - Pytorch 的安装步骤了解如何将原生 Pytorch 的模型代码是如何适配到 Ascend - Pytorch 前置知识 对原生 Pytorc…

足够惊艳,使用Alpaca-Lora基于LLaMA(7B)二十分钟完成微调,效果比肩斯坦福羊驼

之前尝试了从0到1复现斯坦福羊驼&#xff08;Stanford Alpaca 7B&#xff09;&#xff0c;Stanford Alpaca 是在 LLaMA 整个模型上微调&#xff0c;即对预训练模型中的所有参数都进行微调&#xff08;full fine-tuning&#xff09;。但该方法对于硬件成本要求仍然偏高且训练低效…

Java基础——IO流+字节流使用

&#xff08;1&#xff09;IO流的概述&#xff1a; IO流也称为输入&#xff0c;输出流&#xff0c;就是用来读写数据的。I表示input&#xff0c;是数据从硬盘文件读入到内存的过程&#xff0c;称之输入&#xff0c;负责读。O表示output&#xff0c;是内存程序的数据从内存到写…

CSS:transform顺序问题(translate()+rotate())

问题&#xff1a;下面两行代码在执行效果上有区别吗&#xff1f; transform: translate(100px,100px) rotate(45deg);transform: rotate(45deg) translate(100px,100px);translate(X,Y)&#xff0c;可以使元素在x轴和y轴上平移。&#xff08;在translate中&#xff0c;x轴右为…

设计模式之观察者模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、观察者模式是什么&#xff1f; 观察者模式是一种行为型的软件设计模式&#xff0c;定义对象间的一种一对多的依赖关系&#x…

51 openEuler搭建PostgreSQL数据库服务器-安装、运行和卸载

文章目录51 openEuler搭建PostgreSQL数据库服务器-安装、运行和卸载51.1 安装51.2 运行51.2.1 初始化数据库51.2.2 启动数据库51.2.3 登录数据库51.2.4 配置数据库账号密码51.2.5 退出数据库51.2.6 停止数据库51.3 卸载51 openEuler搭建PostgreSQL数据库服务器-安装、运行和卸载…

【Webpack5】核心原理

介绍 本章节我们主要学习&#xff1a; loader 原理自定义常用 loaderplugin 原理自定义常用 plugin Loader 原理 loader 概念 帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。 loader 执行顺序 分类 pre&#xff1a; 前置 loadernormal&#xff1a; 普通 …

【golang项目-GeeCache】动手写分布式缓存 day1 - 实现LRU算法

介绍 LRU 内存淘汰算法 LRU(Least Recently Used) 最近最少使用 算法 &#xff0c;系统认为如果这个数据最近使用过那么它被再次使用的概率会高&#xff0c;所以系统会先淘汰最久没被使用的数据 基本逻辑 -----------------------------------------------------------------…