Linux下的Makefile与进度条程序

news2024/11/5 6:03:20

目录

Linux下的Makefile与进度条程序

Makefile与make

Makefile与make介绍

创建第一个Makefile并使用make

Makefile文件基本格式介绍

Makefile依赖方法执行过程

Makefile通用写法

进度条程序

实现效果

前置知识

回车(\r)与换行(\n)

输出缓冲区

实现进度条


Linux下的Makefile与进度条程序

Makefilemake

Makefilemake介绍

在Linux中,Makefile是一个文件,make是一个指令,当使用make指令时,该指令会在当前目录下找Makefile文件从而执行内部的内容

创建第一个Makefile并使用make

首先,在当前目录下创建一个Makefile文件(也可以写成makefile),例如:

接下来在同级目录下创建一个code.c文件

使用vim编辑器输入下面的内容:

#include <stdio.h>

int main()
{
    printf("hello linux\n");
    return 0;
}

保存code.c文件后退出当前vim,使用vim打开Makefile文件,输入下面的内容:

code:code.c
    gcc -o code code.c
.PHONY:clean
clean:
    rm -rf code
需要注意, gcc -o code code.crm -rf code前方是一个Tab键的大小,而不是4个或者8个空格

保存Makefile文件后退出当前vim,在当前目录下输入make指令即可在当前目录下创建code.c对应的可执行文件(具有可执行权限并且文件本身可执行)code,例如下图:

通过常规方式运行该可执行文件./code即可看到打印输出的内容:

接着使用make clean指令清理刚才生成的可执行文件code

Makefile文件基本格式介绍

以前面例子中的Makefile为例:

code:code.c
    gcc -o code code.c
.PHONY:clean
clean:
    rm -rf code
  • 第一行中的code:code.c代表依赖关系,code表示目标文件,code.c表示依赖文件列表中的文件,第二行的gcc -o code code.c代表依赖方法(指令)
  • 第三行中的.PHONY表示生成一个伪目标,clean表示伪目标的名字(可以类比变量名)
  • 第四行及第五行与第一行及第二行含义一致,表示依赖关系和依赖方法,而因为clean没有需要依赖的文件,所以clean:后没有任何依赖文件列表文件

依赖关系:表示两个文件之间构成的一定关系,比如父子关系

依赖方法:通过依赖方法可以执行的对应的指令

依赖文件列表:code.c所处的位置即为依赖文件列表,为了生成目标文件code而需要的文件称为依赖文件,依赖文件列表可以含有不止一个文件

注意:理论上来说,依赖文件列表中的 code.c在当前情况下可以不写,但是如果不写,在第一次执行 make指令后,不论之后 code.c是否修改,再执行 make指令都无法执行对应的依赖方法,因为 code文件已经存在,所以为了保证可以修改,需要加上 code.c

从上面的运行结果可以看出,每一次执行make时都会在控制台回显出对应的依赖方法,如果将编译指令改为echo "测试",则效果如下:

可以看到先回显了对应的依赖方法,再执行依赖方法,如果不希望出现这种情况,可以在执行的指令前加上@使指令不再回显,所以上面的Makefile可以修改为:

code:code.c
    @echo "测试"

运行结果如下:

所以原始的Makefile可以修改为:

code:code.c
    @echo "Start Compiling..."
    @gcc -o code code.c
    @echo "End Compiling..."
.PHONY:clean
clean:
    @echo "Cleaning code..."
    @rm -rf code
    @echo "End Cleaning..."
一个依赖集中可以有多个依赖方法

此时正常运行结果如下:

如果代码出现错误,则gcc会中断编译,所以此时运行结果如下:


使用.PHONY可以生成一个指定名字的伪目标,伪目标的作用是:清除依赖方法执行时进行的文件时间对比,下面是具体介绍:

首先,在Linux中可以使用stat+文件名查看文件当前的属性,对于code.c有:

执行结果中,主要关注三个部分:AccessModifyChange,这三个部分分别表示文件最近一次的访问时间、文件内容被修改的时间和文件属性被修改的时间

  • Access时间:一般不是特别精确,因为如果一个文件访问一次就需要更新一次访问时间,那么对于多个文件来说,这种操作的消耗对于CPU来说是很大的
  • Modify时间:Modify时间只表示文件内容被修改的时间,如果文件属性时间修改,则不影响Modify时间,但是需要注意,Modify时间一旦改变一般伴随着Change时间改变,因为修改文件内容有时会影响到文件的相关属性(例如文件大小等)
  • Change时间:Change时间只表示文件属性被修改的时间,修改文件属性时间不会影响Modify时间

接着,观察对于没有添加伪目标的Makefile第一部分依赖集,如果code文件已经存在,再一次进行make的效果:

code:code.c
    @echo "Start Compiling..."
    @gcc -o code code.c
    @echo "End Compiling..."

如果此时对code.c文件进行修改,那么执行结果会有所不同:

那么指令是如何知道文件是否被修改呢?就是通过前面提到的Modify时间和Change时间,过程如下图所示:

因为code.c创建的时间早于code.c编译的时间,所以开始时不存在code文件,所以第一次执行make指令时正常执行。

code.c文件未修改时,第二次执行make指令会发现code.cModify时间和Change时间依旧在make之前,因为第一次已经满足了code.c的两个时间在code文件的两个时间之前,所以gcc就不会再进行一次编译。

当修改code.c文件后,code.cModify时间和Change时间改变,导致code.c的两个时间在code文件的两个时间之后,此时gcc就可以正常执行,从而make指令不受影响

而如果再Makefile中为这一部分添加一个伪目标,则可以清除指令中文件时间的对比过程:

.PHONY:code
code:code.c
    @echo "Start Compiling..."
    @gcc -o code code.c
    @echo "End Compiling..."

此时无论执行多少次make指令,都不会出现make指令中gcc因为文件时间对比而导致执行结果不同:

make指令虽然结果完全相同,但是不代表依赖方法没有执行,即文件确实每一次都重新编译

执行完编译部分的make指令,想要执行删除code文件对应的make指令需要在make后加上clean,这个clean代表伪目标名,之所以前面直接使用make就可以执行编译指令,是因为make指令在读取Makefile文件时是从上至下顺序查找,而直接使用make,就会执行第一个依赖集对应的依赖方法,执行完毕后就不会再继续往下读;而对于删除code文件的指令来说,其所在位置时Makefile中的第二个依赖集,所以需要告诉make指令找哪一部分

所以,此处可以看出.PHONY的第二个作用就是声明一个伪目标,通过该伪目标帮助make指令快速定位需要执行的依赖集

如果细心可以发现,对于clean依赖集来说,不论是否有.PHONY都可以无限制执行rm -rf依赖方法,所以可以推断出rm -rf指令本身不会考虑文件的时间属性,但是为什么此处还需要加.PHONY?一方面是为了声明伪目标,另一方面是为了当前依赖集中的其他指令会有时间对比

Makefile依赖方法执行过程

前面学习到,当执行gcc -o code code.c实际上是分成了四步,即:

  1. code.c文件编译生成code.i文件
  2. code.i文件编译生成code.s文件
  3. code.s文件编译生成code.o文件
  4. code.o文件编译生成code可执行文件

将对应的指令写入Makefile中,代码如下:

code:code.o
    gcc -o code code.o
code.o:code.s
    gcc -c code.s -o code.o
code.s:code.i
    gcc -S code.i -o code.s
code.i:code.c
    gcc -E code.c -o code.i

.PHONY:clean
clean:
    @rm -rf code

根据make从上至下的运行顺序,首先执行gcc -o code code.o,但是,因为code.o不存在,并且code.o文件依赖于code.s文件,所以继续执行code.o:code.s对应的依赖方法,以此类推直到最后一条依赖方法gcc -E code.c -o code.i执行向上返回执行前面未执行的依赖方法。整个过程可以理解为在一个栈中操作:

假设此处执行的依赖方法同样进栈

所以执行的结果如下图所示:

实际上,在真正开发中,只需要用到两个部分,如下:

code:code.o
    gcc -o code code.o
code.o:code.c
    gcc -c code.c -o code.o

此时运行结果如下:

Makefile通用写法

在前面的Makefile中,每一个依赖方法都需要在前面的依赖关系部分的文件重新写一遍,为了简化过程,可以使用下面的写法:

TARGET=code
SRC=code.o

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

.PHONY:clean
clean:
    @rm -rf $(TARGET) $(SRC)

上面的代码中,首先创建了两个变量分别代表生成的目标文件code以及第一个依赖集中的依赖文件列表中的文件,在依赖方法中使用了两个自动变量(一般建议大写),分别是$@$<

Makefile中,$@表示生成的目标文件,$<表示从依赖文件列表中取出一个文件,对应的还有$^表示依赖文件列表中的所有文件

而对于gcc来说,在Makefile中可以使用内置变量CC(表示C编译器的名字)代替

如果涉及到多个文件编译,则在SRC%.c处使用空格分隔每一个文件


至此,一个基本的Makefile文件编写语法就这么多,如果需要更详细了解Makefile文件,请移步至此->(待更新)

进度条程序

实现效果

前置知识

回车(\r)与换行(\n)

在C语言或者其他高级语言中,换行(\n)表示回到下一行的开始处,实际上换行的效果并不如此

回车(\r):回到当前光标所在行的开始

换行(\n):前往光标所在行的下一行,但是光标是平行向下移动

输出缓冲区

观察下面程序运行的结果:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello linux\n");
    sleep(2);
    return 0;
}

如果在Linux终端运行该程序,可以看到程序先打印了hello linux,然后等待了2秒才显示prompt提示

这里的 sleep函数不是Windows下的 Sleep函数,但是效果基本一致

将上面的程序修改为下面的程序,再观察效果:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello linux");
    sleep(2);
    return 0;
}

可以看到程序先等待了2秒,然后才打印hello linux

C语言程序默认从上往下顺序执行代码,所以不可能是先执行了sleep(2)才执行printf("hello linux");,出现这种现象的原因就是因为缓冲区的存在,程序在输出时并不会直接将内容输出到显示器上,而是先输出到输出缓冲区,再通过刷新/结束缓冲区将内容打印到屏幕上,而之所以在有\n时会显示再等待就是因为\n刷新了缓冲区,导致内容打印到了屏幕上

如果使用将\n替换为\r则同样会先等待再打印,因为\r也不具备刷新缓冲区的效果,如果在当前情况下想刷新缓冲区但又不想使用\n,则可以使用fflush()函数,传递参数为标准输出stdout,代码如下:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello linux");
    fflush(stdout);
    sleep(2);
    return 0;
}

实现进度条

思路:首先创建3个文件,分别是测试文件main.c、头文件process.h和实现文件process.c。对于进度条,实际上就是先打印原数组内容,再填充数组然后刷新缓冲区,对于百分比,只需要使用循环变量控制即可,对于最右侧闪烁的符号,实际上就是四个动画帧符号,每一次循环加载一个动画帧即可,但是为了循环加载,需要使下标在指定范围内循环,可以考虑循环队列(数组版)下标轮回的思路,所以基本代码如下:

// 实现文件
#include "process.h"

void process()
{
    char bar[NUM] = {0};// 进度条数组
    int count = 0;
    const char *label = "|/-\\";//控制进度条最右侧的闪烁符号(| 顺时针旋转)
    size_t len = strlen(label);
    while(count <= 100) 
    {
        // [%-100s]预留100个字符的位置,每一次打印数组bar中的字符,因为初始化为0,所以数组中全是'\0',当遇到第一个'\0'就停止,加上-表示从右往左(默认从左往右)打印
        // 使用count控制进度条百分比,百分号%需要额外转义
        // label闪烁符号,使用count%4使下标循环在0-3,思路可以联想循环队列数组版控制下标轮回的方式
        printf("[%-100s][%d%%][%c]\r", bar, count, label[count%len]);
        fflush(stdout); // 先刷新缓冲区,打印出停留在缓冲区的内容
        bar[count++] = STYLE; // 向数组中添加字符
        usleep(20000);// 睡眠时间单位为微秒,1秒 = 1000毫秒=1000000微秒
    }
    printf("\n");
}

// 头文件
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define NUM 101 // 定义进度条字符的个数
#define STYLE '#' // 定义进度条的样式

void process();

// 测试文件
#include "process.h"

int main()
{
    process();
    return 0;
}

对应的Makefile如下:

TARGET=process
SRC=process.c main.c

$(TARGET):$(SRC)
    $(CC) $^ -o $@

.PHONY:clean
clean:
    rm -rf $(TARGET)

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

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

相关文章

vue+ThreeJS:从0 到1 搭建开发环境

文章目录 一、下载安装&#xff08;懒人版&#xff09;二、顺序安装1&#xff0c;下载安装nodejs2&#xff0c;安装vue-cli3&#xff0c;创建vue-three 项目。4&#xff0c;安装threeJS5&#xff0c;安装element UI &#xff08;选装&#xff09;最终package.json文件如下&…

C语言深入理解指针3

1.字符指针变量 在指针类型中char*是字符指针 int main() {char ch w;char* pc &ch;//pc是字符指针变量//字符指针变量是用来存放地址的const char* p "abcsefghi";// 不是将abcdefghi\0存放到p中// 而是将首字符a的地址存放在p中// "abcsefghi"是…

逻辑代数的基本规则

目录 逻辑代数的基本规则 带入规则 反演规则 对偶规则 逻辑代数的基本规则 带入规则 将逻辑等式两边的某一变量均用同一个逻辑函数代替&#xff0c;等式仍然成立。 可以用A非代替A&#xff0c;也可以用C代替B。 也可使用BC这样一个整体代替B。 反演规则 可以把与换或&#x…

营养作用的对象是有区别的 第八篇

除了7大营养素 还需要补充其他营养素 食品营养学 临床营养学 大众营养学 食品营养学 你要早点就开始预防

怎么强制撤销excel工作表保护?

经常不是用的Excel文件设置了工作表保护&#xff0c;偶尔打开文件的时候想要编辑文件&#xff0c;但是发现忘记了密码&#xff0c;那么这种情况&#xff0c;我们怎么强制撤销excel工作表保护&#xff1f;今天分享两种解决方法。 方法一、 将excel文件转换为其他文件格式&…

C语言进阶【1】--字符函数和字符串函数【1】

本章概述 字符分类函数字符转换函数strlen的使用和模拟实现strcpy的使用和模拟实现strcat的使用和模拟实现strcmp的使用和模拟实现彩蛋时刻&#xff01;&#xff01;&#xff01; 字符分类函数 字符&#xff1a; 这个概念&#xff0c;我们在以前的文章中讲过了。我们键盘输入的…

通信工程学习:什么是MPC多媒体个人计算机、MCS多媒体计算机系统

一、MPC多媒体个人计算机&#xff08;Multimedia Personal Computer&#xff09; 1、MPC多媒体个人计算机定义 多媒体个人计算机&#xff08;MPC&#xff09;是指具备处理多媒体信息&#xff08;如音频、视频、图像、动画和文本等&#xff09;能力的个人计算机。它不仅具备传统…

html记账本改写:保存数据 localStorage。

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>记账本改写</title><style>table {user-select: none;/* width: 100%; */border-collapse: collapse;}table,th,td {border: 1px solid…

数据集 3DPW-开源户外三维人体建模-姿态估计-人体关键点-人体mesh建模 >> DataBall

3DPW 3DPW-开源户外三维人体建模数据集-姿态估计-人体关键点-人体mesh建模 开源户外三维人体数据集 inproceedings{vonMarcard2018, title {Recovering Accurate 3D Human Pose in The Wild Using IMUs and a Moving Camera}, author {von Marcard, Timo and Henschel, Robe…

从“游戏科学”到玄机科技:《黑神话:悟空》的视角打开动漫宇宙

近日&#xff0c;中国游戏界迎来了一场前所未有的盛事——由游戏科学公司开发的《黑神话&#xff1a;悟空》正式上线&#xff0c;并迅速成为全球玩家热议的焦点。在居高不下的讨论热度中&#xff0c;有人说他的成功在于对《西游记》为背景进行改编&#xff0c;对原著进行了分析…

读软件设计的要素04概念的关系

1. 概念的关系 1.1. 概念是独立的&#xff0c;彼此间无须相互依赖 1.1.1. 一个概念是应该独立地被理解、设计和实现的 1.1.2. 独立性是概念的简单性和可重用性的关键 1.2. 软件存在依赖性 1.2.1. 不是说一个概念需要依赖另一个概念才能正确运行 1.2.2. 只有当一个概念存在…

1 模拟——67. 二进制求和

1 模拟 67. 二进制求和 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例 1&#xff1a; 输入:a "11", b "1" 输出&#xff1a;"100" 示例 2&#xff1a; 输入&#xff1a;a "1010", b "…

单GPU一分钟生成16K高清图像!新加坡国立发布LinFusion:无缝兼容Stable Diffusion插件

论文链接&#xff1a;https://arxiv.org/pdf/2409.02097 Git链接&#xff1a;https://lv-linfusion.github.io/ 亮点直击 本文研究了Mamba的非因果和归一化感知版本&#xff0c;并提出了一种新颖的线性注意力机制&#xff0c;解决了扩散模型在高分辨率视觉生成中的挑战。 本文…

Vue——day11之生命周期

目录 生命周期的八个阶段 生命周期执行的流程图 代码示例 总结 Vue的生命周期是指在Vue实例创建、挂载、更新和销毁过程中&#xff0c;会触发的一系列钩子函数。这些钩子函数可以用来在不同的生命周期阶段执行相应的逻辑操作。 生命周期的八个阶段 Vue的生命周期可以分为…

Github 2024-09-08 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-09-08统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10TypeScript项目1JavaScript项目1Laravel: 以优雅语法简化Web开发 创建周期:4028 天开发语言:PHP协议类型:MIT LicenseStar数量:30…

gazebo 已加载模型但无法显示

目录 写在前面的话问题一&#xff1a;robot_state_publisher 发布机器人信息失败报错一 Error: Error document empty.报错二 .xcaro 文件中有多行注释成功启动 问题二&#xff1a;通过 ros2 启动 gazebo 失败成功启动 问题三&#xff1a;gazebo 崩溃和无法显示模型问题四&…

使用LSTM(长短期记忆网络)模型预测股票价格的实例分析

一&#xff1a;LSTM与RNN的区别 LSTM&#xff08;Long Short-Term Memory&#xff09;是一种特殊的循环神经网络&#xff08;RNN&#xff09;架构。LSTM是为了解决传统RNN在处理长序列数据时遇到的梯度消失或梯度爆炸问题而设计的。 在传统的RNN中&#xff0c;信息通过隐藏状…

电动机制造5G智能工厂工业物联数字孪生平台,推进制造业数字化转型

电动机制造5G智能工厂工业物联数字孪生平台&#xff0c;推进制造业数字化转型。5G智能工厂与物联数字孪生平台的融合应用&#xff0c;为电动机制造业的数字化转型铺设了一条高速通道。这一创新模式不仅极大地提升了生产效率&#xff0c;还深刻改变了产品的设计、生产、管理及运…

在全球化时代成为超级个体:Web3、个人品牌与AI工具的融合

随着Web3技术和人工智能的快速发展,个人品牌建设与创作者经济正在迎来前所未有的机遇。《Web3Brand》是一个专注于帮助用户理解Web3技术、建立和增强个人品牌、提升创作者经济实力,并利用AI工具提高工作效率的平台。本文将探讨该博客如何通过提供播客、案例分析、策略指南和工…

redis内存清理和linux系统清理缓存以及redis启动

1清空所有数据库 redis-cli FLUSHALL 2清空所有数据库redis-cli FLUSHDB 3. 删除指定的缓存键 redis-cli DEL <key>4. 设置键过期 redis-cli EXPIRE <key> <seconds>例如&#xff1a; redis-cli EXPIRE mykey 605.启动redis 这个启动命令要在/usr/loca…