一、背景
1.1概述
代码编程可执行文件,叫做编译(compile);在一个项目中,先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。
make是最常用的构建工具,诞生于1977年,主要用于C语言想项目。
实际上,任何只要某个文件发生变化,就要重现构建的项目都可以用make构建。
1.2介绍
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
1.3 make的概念
make: 制作。比如,要做出文件a.txt,就可以执行:
$ make a.txt
make本身不知道如何做出a.txt,需要告知make如何调用其它命令完成这个目标。
比如,假设文件a.txt依赖于b.txt和c.txt,是后面两个文件连接(cat命令)的产物。那么,make需要知道下面的规则:
a.txt: b.txt c.txt cat b.txt c.txt > a.txt
也就是,make a.txt这条命令的背后实际分为两步:
确认b.txt和c.txt必须已经存在;
使用cat命令将两个文件进行合并,输出为新的文件。
像这样的规则,需要写在一个名为Makefile的文件中,make命令依赖这个文件进行构建。
Makefile也可以写成makefile,或者用命令行参数指定为其它文件名:
$ make -f rules.txt $ make --file=rules.txt
make只是一个根据指定的Shell命令进行构建的工具。
1.4 Makefile的文件格式
Makefile文件由一系列规则构成。每条规则的形式如下:
<target>: <prerequisites>
[tab] <command>
目标: 前置条件
[必须有个table键] <命令>
“目标”是必须的;“前置条件”和“命令”是可选的,但两者必存其一。
明确:构建目标的前置条件是什么,以及如何构建。
第一行是依赖关系,第二行必须以Tab键开头!!!,第二行写的是依赖方法
依赖关系可以为空
目标(target)
一个目标(target)构成一个规则。目标通常是文件名,指明命令所要构建的对象,比如a.txt。
目标可以是一个文件名,也可以是多个文件名,之间用空格分隔。
除了文件名,目标还可以是某个操作的名字,这称之为“伪目标”(phony target)。
clean:
rm *.o
上面代码的目标是clean,不是一个文件名,是一个操作的名字,属于“伪目标”,作用是删除文件。
$ make clean
为了避免这种情况,可以声明clean是“伪目标”:
.PHONY: clean
clean:
rm *.o
声明clean是“伪目标”之后,make不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。
如果make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。
前置条件(prerequisites)
前置条件通常是一组文件名,之间用空格分隔。它指定了“目标”是否重新构建的判断标准: 只要一个前置文件不存在,或者更新过,“目标”就需要重新构建。
result.txt: source.txt
cp source.txt result.txt
构建result.txt的前置条件时source.txt。如果当前路径下,source.txt已经存在,那么make result.txt可以正常运行;否则必须再写一条规则,用以生成source.txt。
source.txt:
echo “this is the source” > source.txt
source.txt没有前置条件,意味着跟其它文件无关,只要source.txt不存在,每次调用make source.txt,都会生成source.txt。
连续执行两次make result.txt。
第一次会先创建source.txt,再创建result.txt。
第二次执行,make发现source.txt没有更新,就会不执行任何操作。
命令(command)
命令(command)表示如何更新目标文件,由一行或多行Shell命令组成;
是构建“目标”的具体指令;
结果通常是生成目标文件。
每行命令之前必须有一个tab键。如果想用其它键,可以用内置变量.RECIPEPREFIX声明。
.RECIPEPREFIX=>
all:
>echo 123
用.RECIPEPREFIX指定大于号(>)替代tab键。
每行命令都是在一个单独运行的Shell中执行的,这些Shell间没有继承关系。
var:
export foo=bar
echo “foo=[$$foo]”
解决方法:
-
;
-
\
-
.ONESHELL
补充:gcc如何得知,源文件不需要再编译了呢?
Modify代表文件内容被修改的时间,Change代表文件属性被修改的时间,Access代表最后一次访问文件的时间,值得注意的是,文件大小也算文件的属性
有时候访问文件的时间Access被更新的不是很灵敏,以前老的操作系统内核,对于这件事的原则就是,只要你访问了,就立马更新时间,但现在的操作系统内核,过一段时间之后才会更新我们的访问文件时间。
原因:文件操作的时候,改文件就一定会访问文件,但访问文件不一定该文件,所以访问文件的次数太多了,要进行更多次的IO,并且文件访问的时间基本没有人关心。
1.5示例
改进
cc 命令
在 Linux 的 makefile 中,cc 命令通常用于编译 C 语言程序。cc 命令可以使用以下选项:
- `-c`:编译源文件,生成目标文件。
- `-o`:指定编译生成的目标文件名。
- `-Wall`:显示所有警告信息。
- `-g`:生成调试信息,用于 gdb 调试。
- `-I`:指定头文件路径。
- `-L`:指定库文件路径。
- `-l`:指定要链接的库文件名。例如,假设有一个名为 `main.c` 的源文件,需要编译生成可执行文件,可以使用以下 makefile:
CC = cc CFLAGS = -Wall -g -I./include LDFLAGS = -L./lib -lm OBJS = main.o foo.o bar.o program: $(OBJS) $(CC) -o $@ $(LDFLAGS) $(OBJS) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -rf *.o program
在上面的 makefile 中,`CC` 定义了编译器为 `cc`,`CFLAGS` 定义了编译选项,`LDFLAGS` 定义了链接选项,`OBJS` 定义了需要编译的目标文件。`program` 是最终生成的可执行文件名,它依赖于 `$(OBJS)` 中的目标文件,通过 `$(CC)` 命令进行链接生成。`%.o: %.c` 表示根据 `.c` 文件生成 `.o` 文件。`clean` 是清理编译过程中生成的目标文件和可执行文件。
二、Linux的第一个小程序-进度条
2.1缓冲区概念
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 printf("you can see me ........\n");
7 sleep(2);
8 return 0;
9 }
// 可以利用fflush(stdout)语句来刷新缓冲区,这样数据就会立马显示出来
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 printf("you can see me ........");
7 sleep(2);
8 return 0;
9 }
如果加上\n那就会先显示you can see me然后停下来2秒钟,如果没有\n的话,那就会先停下来2秒,然后才显示出you can see me。
第一种拥有\n可以立即输出的原因是因为,\n是行缓冲,只要遇到\n就会立马将这一行的内容输出到显示器上面
针对第二种没有\n的情况,首先printf语句肯定是要先执行的,唯一的可能性就是,这个you can see me没有被立即显示出来,等到走完sleep语句,它才显示出来,在未显示的这段时间里面,you can see me一直被存储在行缓冲区里面
2.2\r && \n
理论上,回车和换行不是一个概念,换行是回到当前行的最开始,换行是将光标挪到下一行相同的光标位置,\r是回车\n是换行,\r\n合起来我们称之为回车换行。
语言层面上,\n就是回车换行,因为编译器在内部对于\n做了特殊处理,回车换行其实分成两步,先换行,再回车。
我们看一下回车\r
不显示的原因就是我们的回车\r将之前的内容给覆盖掉了,并且在缓冲区中回到了这一行的起始位置,因此程序结束也并没有打印。
2.3fflush(stdout)
因此为了解决上面的问题,可以用刷新缓冲区的办法实现
2.4 倒计时实现
显示器能够显示各种符号,包括数字,汉字,字母等等,这些都被计算机看作符号,只不过人把它分为数字还是汉字,字母等,能够显示这么多符号是因为显示器面板上有各种像素点,点亮对应显示器上的像素点,就可以给人类显示出来各种各样的符号了,并且凡是能够显示到显示器上的,其实都是字符
通过上面的了解,大家已经知道了缓冲区的概念,因此为了下面的进度条实现,在这里我们先通过上面的知识实现一下倒计时:
上述实际上有一定的细节,我们知道/r只是回到起始位置,但如果不控制格式2d,就会出现打印10,90,80……的情况,因为我们每次只覆盖了第一个位置,因此在这里要控制格式,并且fflush(stdout)。
2.5进度条
对于进度条来说,通过最上面的航缓冲的知识,我们已经知道应该如何去规避了,因此在这里直接展示进度条,我将程序分成三个部分,即经典的main.c/process.c/process.h,并且将makefile中的依赖对象也改变,对于依赖对象来说,只要-o后面最靠近的是要生成的即可。
接下来我们看看代码,并将其执行:(主程序)