文章目录
一、make和makefile的区分
make是一个命令,makefile是一个文件
二、依赖关系和依赖方法
依赖关系: 依赖关系指明了两个文件之间的依赖性。例如test.o文件的生成要依赖test.s文件
依赖方法: 依赖方法就是我依赖这个关系需要做什么。例如test.o形成需要test.s进行汇编,即gcc -c test.s -o test.o
三、make/Makefile的使用
#include <stdio.h>
int main()
{
printf("hello Makefile\n");
return 0;
}
如上一串代码,如果我们需要在Linux中对其进行编写,生成可执行程序,以往我们需要敲一长串指令gcc test.c -o test
,当我们学会makefile之后我们就不在需要敲这一长串指令了,只需要一个make指令即可。
1、makefile的编写
-
首先在源文件的目录下创建一个makefile或者Makefile文件(首字母可以大写,其他的不行)
-
然后打开makefile文件,进行规则的编辑:
第一行中冒号左边的mycode
是目标文件,该目标文件依赖于冒号右边的mycode.c
而产生,所以第一行写的是依赖关系,第二行必须使用一个tab键之后写上两个文件的依赖方法。 -
接着然后直接使用
make
命令即可。如下:
这样就形成了我们所需要的可执行程序文件。
2、临时文件的清理
完成上面的编写后我们就可以生成需要的可执行文件,但若我们需要清理生成的临时可执行程序文件,则可以进行如下编写:
.PHONY
表示被该关键字修饰的对象是一个伪目标。(该伪目标总是可被执行)
使用: 如下图:
疑问: 为什么编译的时候直接使用make就可以了,而进行清理需要加上clean呢?
其实make后面也可以接mycod,只不过make默认对makefile文件中的第一个目标文件可以省略名称
3、makefile的工作原理
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 默认情况下Makefile的第一个目标为终极目标。
即默认情况下,makefile只形成一个可执行目标文件,形成之后,后续的依赖关系和依赖方法不再执行(默认从上到下扫描总是执行一个,默认不指明情况下,只形成第一个) - all:Makefile文件默认只生成第一个目标文件即完成编译,但是我们可以通过all 指定所需要生成的目标文件。
- 若需要完成指定的功能,只需在make命令后加上自己写入makefile中的命令
4、文件的三个时间
上方我们提到,.PHONY
是修饰伪目标的,该伪目标总是可被执行。什么意思呢?如图:
进行第一次make的时候,我们可以进行编译生成可执行程序文件,但当我们进行第二次make的时候就不可以了而且提示我们说mycode是最新的,而我们由.PHONY
修饰的clean
就可以总是被执行的。
那为什么gcc编译知道mycode是最新的呢?接下来我们认识一下文件的三个时间:
[wyt@VM-20-4-centos lesson3]$ stat mycode.c
File: ‘mycode.c’
Size: 81 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 794005 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1001/ wyt) Gid: ( 1001/ wyt)
Access: 2023-02-11 17:08:52.893235379 +0800 //文件最近访问时间
Modify: 2023-02-04 13:21:39.841893206 +0800 //内容被修改时间
Change: 2023-02-04 13:21:39.841893206 +0800 //属性被修改时间
Birth: -
4.1、Access:最近一次访问文件的时间
读取文件内容,修改文件内容,Access都会发生变化.
修改文件内容,Access的时间会立即更新;但是读文件操作,不会立即更新。
我们读取文件或者查看文件,如cat、find等操作,这些都属于高频操作,如果我们频繁使用这些命令,可能会频繁更新Access时间,导致Linux的处理速度下降
在较新的Linux内核中,这个问题被优化了,对文件进行读操作以后,Access时间不会立即更新,经过一定的时间间隔,OS才会自动进行更新时间
4.2、Modify:最近一次修改文件内容的时间
只要是修改文件内容,Modify的时间都会被更新,这个被视为低频操作,所以一般修改文件内容以后,会立即刷新。
但是修改文件内容,有可能会顺带着修改文件属性,比如新增内容会改变文件大小(文件大小属于文件属性)
4.3、Change:最近一次修改文件属性的时间
修改文件属性也被视为低频操作,修改文件属性会立即更新Change的时间
只是单纯的修改文件属性,不会影响到Modify和Access的时间
4.4、‘xxx’ is up to date问题
所以我们在上面遇到的make问题就很好解答了。
第一次make后,生成一个可执行目标文件,再之后make时,需要比较可执行目标文件mycode
和源文件mycode.c
的Modify的时间
可执行目标文件mycode
的modify的时间比源文件mycode.c
的modify时间新,所以不可以再进行编译了。
当我们的源文件时间进行修改后,我们又可以再次进行make编译了
总结:
当源文件的Modify时间比目标文件的Modify时间新时,才可以再次利用make进行编译。
四、makefile的推导规则
makefile文件在扫描时,是从上往下进行扫描的,比如下图:
make会一层一层地去找文件的依赖关系,直到最后编译出第一个目标文件。
拿上图举例:
如上,最后找到mycode.i目标文件后,就会在一层一层的往上,最后形成一个可执行的目标文件。
上述的推导过程就如同一个栈结构,先进后出。
五、进度条小程序
1、行缓冲区问题
先看下面两串代码执行的现象:
代码1:
现象:
代码2:
现象:
两串代码的区别仅仅就是代码1有\n
,而代码2没有那为什么会造成不同的现象呢?
首先我们要明确一点就是,代码是顺序结构的,所以他是从上往下执行,所以先执行printf在执行sleep。至于为什么会有不同的现象,如下:
,这里就有一个行缓冲区的概念在这里。对于C语言级别的缓冲区而言,任何字符串都会先保存在这个缓冲区里面,等待刷新在显示屏上。但是显示器刷新是属于行刷新,就是遇到‘\n’就进行刷新。所以现在我们就理解了,代码2中没有’\n’,要打印的字符串一直保存在了C语言级别的缓冲区,只有程序运行快结束是才会把这些字符串刷新在屏幕上。
那我们该如何将代码2的printf打印立马显示到显示器上呢?
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello LInux");
fflush(stdout);//fflush会立即刷新缓冲区
sleep(2);
return 0;
}
2、回车换行(\r与\n)
理解:
-
\r
:回车。回到当前行的最开始 -
\n
:换行。换到下一行,但列不变
在语言层面:\n
就是回车换行
3、倒计时功能
#include <stdio.h>
#include <unistd.h>
int main()
{
int cnt=10;
while(cnt)
{
printf("%2d\r",cnt);//2d控制刷新两位,\r表示回车
fflush(stdout);//手动刷新缓冲区
sleep(1);
--cnt;
}
return 0;
}
3、进度条的实现
多文件形式:
头文件process.h
#pragma once
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define NUM 101
extern void ProncessOn(); //函数的声明
源文件process.c
#include "process.h"
void ProncessOn() //函数的定义
{
int cnt = 0;
char bar[NUM];
memset(bar, '\0', sizeof(bar));
const char *lable = "|\\-/";
while(cnt <= 100)
{
printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt%4]);
fflush(stdout);//立即打印
bar[cnt++] = '#';
//sleep(1);//单位是秒,太慢了
usleep(50000); //微妙 5S/100 == 0.05S == 50000
}
printf("\n");
}
效果如下: