【Linux03-基本工具之make和makefile】Linux下的项目构建工具+进度条小程序

news2024/9/24 5:31:52

前言

接上篇,接着学习基本工具。

博主水平有限,不足之处望请斧正。

三、make和makefile

是什么

makefile(Makefile):用来写入 依赖关系和依赖方法 的文件。

make:用来执行 makefile 的命令。


为什么

允许我们自动化构建项目,方便。


怎么用

写出makefile ==> make执行。

先见见猪跑:

[bacon@VM-12-5-centos 3-make]$ touch makefile
[bacon@VM-12-5-centos 3-make]$ vim makefile
[bacon@VM-12-5-centos 3-make]$ ls
makefile
[bacon@VM-12-5-centos 3-make]$ touch test.c
[bacon@VM-12-5-centos 3-make]$ vim test.c 
[bacon@VM-12-5-centos 3-make]$ make
gcc test.c -o test 
[bacon@VM-12-5-centos 3-make]$ ls
makefile  test  test.c
[bacon@VM-12-5-centos 3-make]$ ./test 
hello makefile

test.c:

#include <stdio.h>
int main()
{
    printf("hello makefile\n");
    return 0;
}

makefile:

test:test.c
	gcc test.c -o test 
.PHONY:clean
clean:
  rm -f test

“test”:目标文件(要得到的文件)。

“test.c”:依赖文件(得到目标文件的基础)。

.PHONY(adj. <口>假的;欺骗的):伪目标修饰(被修饰的目标成为伪目标,每次都生成

“clean”:没有依赖文件的伪目标,每次都会执行。

"gcc test.c -o test ":得到目标文件test的方法。

"rm -f test ":得到目标文件clean的方法。

以上就构成了makefile的全部要素:依赖关系和依赖方法。

怎么理解呢?

又月初了,大学生张三的生活费也差不多了,需要打个电话给老爸要钱。做成这件事必要的两个要素,

张三打电话给他老爸:“老爸,我是你儿子。”——依赖关系:张三依赖他老爸。

张三接着说:“该打点生活费了哈。”——依赖方法:这件事上,张三通过要钱来对他老爸产生依赖。

为什么说是必要的呢?

张三打电话给李四的老爸:“打点生活费哈。”——依赖关系不成立:张三不依赖李四老爸。

张三打电话给他老爸:“老爸,我是你儿子。”(随即挂断电话)——没表明依赖方法:张三老爸一头雾水。

到这里,我们再看makefile:

test:test.c#依赖关系
	gcc test.c -o test #依赖方法
.PHONY:clean#指定clean为伪目标(总是生成)
clean:#依赖关系
  rm -f test#依赖方法

“test” 通过 "gcc test.c -o test "的方法 依赖"test.c"而生。

“.PHONY clean” 总是通过 "rm -f test " 的方法 不依赖任何文件而生。


再看,

当我们将已经编译且未修改过的文件再次编译:

[bacon@VM-12-5-centos 3-make]$ make
make: `test' is up to date.
【make是怎么知道不需要再编译的?】
[bacon@VM-12-5-centos 3-make]$ stat test.c
  File: ‘test.c’
  Size: 80        	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 921768      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1003/   bacon)   Gid: ( 1003/   bacon)
Access: 2022-12-01 19:27:01.871065494 +0800
Modify: 2022-12-01 19:27:01.515064249 +0800
Change: 2022-12-01 19:27:01.515064249 +0800
 Birth: -
[bacon@VM-12-5-centos 3-make]$ stat test
  File: ‘test’
  Size: 8360      	Blocks: 24         IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 921769      Links: 1
Access: (0775/-rwxrwxr-x)  Uid: ( 1003/   bacon)   Gid: ( 1003/   bacon)
Access: 2022-12-01 19:28:21.868345278 +0800
Modify: 2022-12-01 19:28:21.485343938 +0800
Change: 2022-12-01 19:28:21.485343938 +0800
 Birth: -
  • Access: 指最后一次读取的时间。

    (一定时间/次数后才更新,读取操作太频繁,总是更新要进行更多IO,慢,也因为这个时间没那么重要,没必要随时更新)

  • Modify: 指最后一次修改内容的时间。

  • Change: 指最后一次修改属性的时间。

Access和Modify的验证:

[bacon@VM-12-5-centos 3-make]$ stat test.c
  File: ‘test.c’
  Size: 144       	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 921768      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1003/   bacon)   Gid: ( 1003/   bacon)
Access: 2022-12-01 19:33:55.887513525 +0800
Modify: 2022-12-01 19:33:55.520512242 +0800
Change: 2022-12-01 19:33:55.520512242 +0800
 Birth: -
[bacon@VM-12-5-centos 3-make]$ vim test.c
[bacon@VM-12-5-centos 3-make]$ stat test.c
  File: ‘test.c’
  Size: 240       	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 921768      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1003/   bacon)   Gid: ( 1003/   bacon)
Access: 2022-12-01 19:34:17.867590404 +0800
Modify: 2022-12-01 19:34:17.290588387 +0800
Change: 2022-12-01 19:34:17.290588387 +0800
 Birth: -

我们vim进行读取了,access变,也改变了内容,modigy变,

【但是为什么change也变??】

:内容改变了,文件大小也改变,文件大小也是属性呀!

Change的验证:

[bacon@VM-12-5-centos 3-make]$ chmod a+r test.c
[bacon@VM-12-5-centos 3-make]$ stat test.c
  File: ‘test.c’
  Size: 240       	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 921768      Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1003/   bacon)   Gid: ( 1003/   bacon)
Access: 2022-12-01 19:34:17.867590404 +0800
Modify: 2022-12-01 19:34:17.290588387 +0800
Change: 2022-12-01 19:38:12.245410193 +0800
 Birth: -

好像,通过源文件和可执行程序的Modify时间就可以确定:

源文件更加地新:可执行程序已经落后了,需要重新编译。

源文件更加地新:可执行程序还没修改,不需要再编译了。

(想验证的话可以通过touch某个已存在文件来更新全部时间)

所以.PHONY的本质就是不略过这个“对比时间”的规则。

如果我们用.PHONY将test这个目标文件也变成伪目标,又能强制重复编译:

.PHONY:test 
test:test.c
	gcc test.c -o test 
.PHONY:clean
clean:
	rm -f test
[bacon@VM-12-5-centos 3-make]$ make
gcc test.c -o test 
[bacon@VM-12-5-centos 3-make]$ make
gcc test.c -o test 
[bacon@VM-12-5-centos 3-make]$ make
gcc test.c -o test 
[bacon@VM-12-5-centos 3-make]$ make
gcc test.c -o test

makefile了解得差不多了,但make不只是单纯直接执行就万事大吉了。

  • make默认生成第一个目标文件——所以我们直接make就可以生成,要是想也可以指定生成第一个
  • make默认只生成一个目标文件
[bacon@VM-12-5-centos 3-make]$ make test
gcc test.c -o test 
[bacon@VM-12-5-centos 3-make]$ make clean
rm -f test
[bacon@VM-12-5-centos 3-make]$ make test; make clean
gcc test.c -o test 
rm -f test

makefile的推导规则

想了解它的推导规则,我们得把makefile复杂化一下:

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


.PHONY:clean
clean:
	rm -f test.i test.s test.o test

这样其实才是真正过程:

.c ==预处理==> .i ==编译==> .s ==汇编==> .o ==链接.o==> 可执行

那我得先生成下面的.i才能一步步往后走啊?

对的,当make在当前目录找不到test依赖的test.o,就会往下走;又看到当前目录下没有test.o依赖的test.s,就又往下走;直到走到要生成test.i的时候,当前目录下有test.c,就一步步往回执行。

makefile的推导规则就像栈一样。

*并不建议这样写,直接用.c一步到位就很好,上面只是为了帮助理解。


第一个小程序:进度条

预备知识

1. 缓冲区问题

我们先来通过一个简单程序看一个现象:

#include <stdio.h>
#include <unistd.h>//休眠函数头文件


int main()
{
    printf("i'm here!\n");//带\n
    
    sleep(3);//休眠函数

    return 0;
}

在这里插入图片描述

当我们去掉/n:

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


int main()
{
    printf("i'm here!");

    sleep(3);

    return 0;
}

在这里插入图片描述

【明明先执行printf,才执行sleep,为什么先sleep才能看到数据被打印?】

我们的代码是顺序结构,先prinf后sleep,为什么数据休眠完才打印?

这涉及到缓冲区的概念:执行sleep前,printf一定执行完了。不过printf打印的数据而没有立即刷新到显示器,而是打印到缓冲区。而sleep执行完,程序退出,才再刷新缓冲区。就看到了“先sleep,后printf”的现象,其实只是看不见printf先执行,而不是它后执行。

【为什么不立即刷新缓冲区或是直接打印到显示器?】

因为显示器是外设,速度太慢,频繁的访问会导致效率降低——所以我们按需刷新(显示器默认一行刷新一次)。

*今天不过多解释缓冲区的概念,以免提高学习成本。

我们也可以手动刷新缓冲区:

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


int main()
{
    printf("i'm here!");

    fflush(stdout);//刷新缓冲区的函数

    sleep(3);

    return 0;
}

在这里插入图片描述

不通过显示器默认的行刷新来刷新,而是手动刷新,也能达到效果。

2. 回车换行的概念

回车和换行其实不是一个概念:

  • 回车:光标移动到行首
  • 换行:光标移动到下一个行同一位置

只不过我们想要的效果是 回车 + 换行,所以键盘上的ENTER键就直接是 回车+换行 的效果
在这里插入图片描述

(较早期的键盘,键帽形状都很形象)

一般 \r 是回车,\n是换行,不过语言给我们做了处理,\n直接就是回车换行。

进度条

我们要实现的效果大概是:

先来个倒数感受一下:
makefile:

process:main.c process.c
	gcc main.c process.c -o process 
.PHONY:clean
clean:
	rm -f process
//process.h
#pragma once 

#include <stdio.h>

void ProcessOn();

//process.c
#include "process.h"

void ProcessOn()
{
    printf("test\n");
}

//main.c

include "process.h"

int main()
{
    ProcessOn();

    return 0;
}
[bacon@VM-12-5-centos process]$ ./process 
test

makefile中不需要写.h相关的操作,因为.h就在当前目录下能找到。

再来实现进度的倒数(百分比):

倒计时当然不能用\n换行,不是我们要的效果。所以换行,打印完一个数字后换行到行首再次覆盖式打印,就能原地倒数。

void ProcessOn()
{
    int cnt = 9;

    while(cnt)
    {
        printf("On: %d\r", cnt--);
      
      	sleep(1);//休眠函数,让程序休眠1秒
    }
}
[bacon@VM-12-5-centos process]$ ./process 
[bacon@VM-12-5-centos process]$ 

程序没问题啊,为什么不打印信息?

还是缓冲区的问题,显示器默认行缓冲,咱这里换行无法刷新,所以手动刷新。

void ProcessOn()
{
    int cnt = 9;

    while(cnt)
    {
        printf("On: %d\r", cnt--);

        fflush(stdout);

        sleep(1);
    }
}

在这里插入图片描述

再试试10秒:

int cnt = 10;

在这里插入图片描述

这可咋办?格式控制

printf("On: %2d\r", cnt--);

在这里插入图片描述

倒数的道理其实和进度条差不多,主要就是缓冲区问题和回车的概念,再来看看进度条吧!


//process.h
#pragma once 

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


#define SYMBOL '#'
#define BARSIZE 101 //100个填充符号 + \0

void ProcessOn();

//process.c
#include "process.h"

void ProcessOn()
{
    char bar[BARSIZE] = "";
    int cnt = 0;

    while(cnt <= 100) // [0, 100]
    {
      	//打印 1/2/.../100个SYMBOL
      	//-100控制右对齐
      	//%将%转义,让它不是格式控制前缀,只是正常的字符
        printf("[%-100s] [%d%%]\r", bar, cnt); 

        bar[cnt++] = SYMBOL;

        fflush(stdout);
        //sleep(1); //每1%停1秒太慢,咱们这里让他5秒跑完:可以用usleep(),单位微秒(1微秒 = 1秒/1百万
        usleep(50000); //1秒打印20次,1次需要 1/20 秒, 再乘1百万,1百万/20 = 5万

    }

    printf("\n完成!\n");
}


//main.c
#include "process.h"

int main()
{
    ProcessOn();

    return 0;
}

在这里插入图片描述

但,当进度不推进(卡住)的时候,我们也没法看出来呀,搞个动态的标识就好了!最终代码和效果如下:

//process.h
#pragma once 

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


#define SYMBOL '#'
#define BARSIZE 101 //100个填充符号 + \0


void ProcessOn();


//process.c
#include "process.h"

void ProcessOn()
{
    char bar[BARSIZE] = "";
    char rotate[] = {'|', '/', '-', '\\'};
    int cnt = 0;

    while(cnt <= 100) // [0, 100]
    {
        printf("[%-100s] [%-3d%%][%c]\r", bar, cnt, rotate[cnt % sizeof(rotate)]); //打印 1/2/.../100个SYMBOL

        bar[cnt++] = SYMBOL;

        fflush(stdout);
        //sleep(1); //每1%停1秒太慢,咱们这里让他5秒跑完:可以用usleep(),单位微秒(1微秒 = 1秒/1百万
        usleep(50000); //1秒打印20次,1次需要 1/20 秒, 再乘1百万,1百万/20 = 5万

    }

    printf("\n完成!\n");
}


//main.c
#include "process.h"

int main()
{
    ProcessOn();

    return 0;
}

在这里插入图片描述


今天的分享就到这里了,感谢观看!

这里是培根的blog,期待与你共同进步,

下期见~

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

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

相关文章

必看知识点:Redis 中的原子操作(1)-Redis 中命令的原子性

必看知识点&#xff1a;Redis 中的原子操作(1)-Redis 中命令的原子性 Redis 如何应对并发访问 Redis 中处理并发的方案 业务中有时候我们会用 Redis 处理一些高并发的业务场景&#xff0c;例如&#xff0c;秒杀业务&#xff0c;对于库存的操作。。。 先来分析下&#xff0c…

CMake中add_executable的使用

CMake中的add_executable命令用于使用指定的源文件向项目(project)添加可执行文件&#xff0c;其格式如下&#xff1a; add_executable(<name> [WIN32] [MACOSX_BUNDLE][EXCLUDE_FROM_ALL][source1] [source2 ...]) # Normal Executables add_executable(<name> I…

芯片漫游指南(1)-- UVM世界观

目录1 uvm中的学习内容2 类库地图3 工厂机制3.1 工厂的意义3.2 工厂提供的便利3.3 覆盖方法4 核心基类4.1 uvm_object4.2 域的自动化4.3 拷贝&#xff08;copy&#xff09;4.4 比较4.5 打印4.6 打包和解包5 phase机制5.1 概述5.2 phase的九个执行机制5.3 UVM仿真顺序6 config机…

Jenkins安装和配置 (一)

(一) 环境准备 准备一台linux虚拟机&#xff0c;我这里选择的是Ubuntu&#xff0c;安装jdk、maven、git环境 Ubuntu 16.04 虚拟机JDK 1.8MAVEN 3.3.9GIT (二) Jenkins下载安装 Jenkins安装支持以下几种方式&#xff1a; npm包下载安装war包下载安装docker镜像下载安装 这里我…

开发一个简单的http模板之序章

流程 1.当通过开发HTTP模块来实现产品功能时&#xff0c;是可以完全享用Nginx的优秀设计所带来的、 与官方模块相同的高并发特性的。不过&#xff0c;如何开发一个充满异步调用、无阻塞的HTTP模块 呢2. 需要把程序嵌入到Nginx中&#xff0c;也就是说&#xff0c;最终编译出的二…

考虑阶梯式碳交易机制与电制氢的综合能源系统热电优化(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

【YOLO系列改进NO.45】首发最新特征融合技术RepGFPN(DAMO-YOLO)

文章目录前言一、解决问题二、基本原理三、​添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv7&#xff0c;已经集合了大量的trick&#xff0c;但是还是有提高和改进的空间&#xff0c;针对具体应用场景下的检测难点&#xff0c;可以不同的改进方法。此后的系列…

[附源码]计算机毕业设计springboot云南美食管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

语义分割在线标注思路

语义分割选择使用多边形框标注&#xff0c;相对于物体检测它多了一项计算mask掩膜的需求。 一个图片&#xff0c;可能会进行多项标注。每项标注里面都会在物体检测的格式基础上增加mask属性&#xff0c;mask解释为图像掩膜&#xff0c;里面存与图像宽高对应大小的二维数组。 此…

什么是蛋白质组学?

导读 本文[1] 将从蛋白质组学的定义&#xff0c;蛋白质组包含的类型&#xff0c;常用技术&#xff0c;面临的挑战等方面&#xff0c;对蛋白质组学进行一个简要的介绍。 1. 定义 “蛋白质组学”一词于 1995 年首次出现&#xff0c;被定义为对细胞系、组织或生物体所有蛋白质的大…

Vue-router 路由间参数传递看完让你明明白白!

目录概述路由传参的两大载体分类声明式路由 【router-link 】编程式路由 【this.$router.push()】传参的方式1. 通过 params 传递参数配置路由参数字段占位&#xff08;编程式路由实现&#xff09;配置路由参数字段占位&#xff08;声明式路由实现&#xff09;无需配置路由参数…

Spring框架(四):Spring命名空间和配置标签

Spring框架&#xff08;四&#xff09;&#xff1a;Spring命名空间和配置标签命名空间介绍配置标签Beans标签DemoImport标签Demoalias标签的Demo和Debug痛定思痛&#xff0c;主要问题出现在自己雀氏不熟悉框架基础、一些面试题&#xff0c;以及sql的使用淡忘了。本章节的开始是…

剩余内存无法满足申请时,系统会怎么做?

文章目录前言内存的分配机制回收可回收内存的类型如何在保证系统性能前提下回收内存可回收类型角度: 调整文件页回收倾向回收的方式角度: 尽早触发kswapd内核线程从计算机CPU架构角度: 采用NUMA如何保护进程不被OOM杀掉总结前言 当我们向操作系统申请内存时候,是否有想过一个问…

Bootstrap页面整合(十二)

首先引入bootstrap&#xff1a; 复制导航条代码&#xff1a; 修改导航条&#xff1a; 删除 改完的导航条&#xff1a; <!--导航条--> <nav class"navbar navbar-inverse"><div class"container-fluid"><!--屏幕自适应的--><d…

【吴恩达机器学习笔记】十、支持向量机

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

神经气体网络(NGN)和不断增长的神经气体网络(GNGN)研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

[附源码]计算机毕业设计springboot影评网站系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Python案例实战,gopup模块,Python3行代码就能获取海量数据

前言 今天给大家分享一个三行代码就能获取海量数据 包含了有指数数据、宏观经济数据、新经济数据、微博KOL数据、信息数据、生活数据、疫情数据等。 开发工具 Python版本&#xff1a; 3.6.4 相关模块&#xff1a; gopup模块 环境搭建 安装Python并添加到环境变量&#…

Linux权限管理(umask、粘滞位)

目录 Linux权限管理 文件访问权限的相关设置方法 目录的权限 umask 粘滞位 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 普通用户&#xff1a;在linux下做有限…

闲鱼话术,必须收藏

今天分享&#xff1a;闲鱼卖货&#xff0c;常用的客服话术。建议收藏 很多学员&#xff0c;货也上了&#xff0c;有人咨询的时候忽然手忙脚乱&#xff0c;懵啊&#xff0c;怎么感觉没人问期待有人问&#xff0c;有人问&#xff0c;自己啥也说不出来了呢?哈哈&#xff0c;不知…