Linux项目自动构建工具–make/Makefile

Linux项目自动构建工具–make/Makefile文章目录零.前言1.什么是make/Makefile2.make/Makefile的使用(1)准备工作(2)依赖关系与依赖方法(…

大家好,欢迎来到IT知识分享网。

零.前言

曾经听过这样一句话:会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。一个工程中的文件不计其数,makefile定义了一系列的规则来决定,哪些文件需要先进行编译,哪些文件要后编译从而进行项目的构建和开发。

1.什么是make/Makefile

我们已经学会了使用gcc或者g++来编译代码形成可执行程序,与VS中不同的是,似乎gcc和g++只能编译一个文件。如果我们想同时编译多个文件呢?就像VS中我们需要同时创建多个.cpp并同时进行编译。
这就需要用到Linux下的项目自动构建工具make和Makefile了。那么这俩货到底怎么用呢?
我们要明确make是一条指令,而Makefile是一个文件。

2.make/Makefile的使用

(1)准备工作

我们首先建立一个文件test.c,运行结果是打印”hello Makefile”。这里不多赘述。
我们要明白我们的目的,是要实现多文件编译。
我们先建立一个Makefile文件,然后打开它:

touch Makefile
vim Makefile

没错,和你想的一样,Makefile就是一个普通的文件,里面空荡荡。需要我们进行添加。

(2)依赖关系与依赖方法

在Makefile中可以调用多个文件,每一个文件必须要有两个条件,那就是依赖关系和依赖方法。
我们在Makefile中写入这样一段内容:

test:test.c   //依赖关系
	gcc test.c -o test  //依赖方法

其中这两行指令分别代表的就是依赖关系和依赖方法。
其中,test是test.c所生成的可执行文件,因此test的生成依赖于test.c,因此称为依赖关系。
注意第二行在开头要空出一个tab的大小。依赖方法就是test.c生成test的过程。
两者缺一不可。
那这样达到的目的是什么呢?

(3)make

此时我们退出vim,并执行make指令,我们发现test已经被test.c执行生成了。
在这里插入图片描述
make指令的作用就是寻找名为makefile或者Makefile的文件,并执行其中的第一条依赖方法
因此这里生成了test可执行程序。
但是当我们再次使用make的时候,会出现这样的说明:
在这里插入图片描述
它认为test已经是最新版本的了,只有修改test.c中的内容,才能重新进行make。
如果我们没有对应的依赖方法文件怎么办?make会自动向下寻找生成该依赖方法文件的内容,这样说比较抽象,我来举一个栗子:
比如我们要生成的编译期间的一系列文件,我们在Makefile中输入如下内容:

 test:test.o
 	gcc test.o
 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  

执行make,make会在Makefile中执行第一条依赖方法,但是却发现我们要生成的test没有依赖的test.o文件,因此make会继续执行下一条依赖关系和依赖方法,发现要想生成.o文件,依然没有依赖其所依赖的test.s还会向下寻找,直到找到生成test.i的依赖关系test.c,再一层一层回溯。make会一层一层去找文件的依赖关系,直到生成第一个目标文件。
此时.i .s .o文件均被创建出来了:
在这里插入图片描述

(4)make clean

当我们使用make创建了test可执行程序后,我们是否有方法也通过make指令进行文件的删除呢?答案是肯定的。
我们还需要对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 test.c test.i test.s test.o test   

.PHONY修饰对应的符号,表示伪目标的概念,它修饰的内容总是可执行的。
比如上面的例子中make就不是总可以被执行,执行一次后如果没有完成文件刷新,就不会再执行了。
而这里定义的clean是可以执行多次的。同时,clean没有依赖方法。
在这里插入图片描述
我们执行make clean指令,发现执行的就是该依赖方法,文件都被删除了,同时我们可以执行多次:
在这里插入图片描述
这里成功执行了多次,只是没有找到已经被删除了的文件,发生了报错。

(5)依赖关系的别名

我们使用来$@代表要生成的文件。
$^代表所依赖的文件。
在这里插入图片描述
这样也是可以生成test可执行程序的。
在这里插入图片描述

3.同时编译多个程序

make/Makefile是项目的自动构建工具,在一个项目中会有多个程序需要编译执行,那么如何一次性编译多个文件呢?
只需要将要生成的可执行文件作为依赖方法即可,makefile会自动去寻找生成它们的方法。

.PHONY:all    
all:mytest myload    
myload:myload.c    
  gcc $^ -o $@    
mytest:mytest.c    
  gcc $^ -o $@                                                                                                                                           
.PHONY:clean    
clean:    
  rm -f myload   

这里定义了一个没有依赖方法的依赖关系,因为make只执行第一个依赖关系和依赖方法,它会寻找让all形成的方式,即执行mytest和myload的生成语句,又因为没有依赖方法,所以all不会被生成。

4.进度条小项目

了解了make/Makefile,我们来做一个进度条的小项目,并通过make/Makefile来完成编译执行。

(1)sleep函数

注意sleep函数由于某种原因,在大部分win下的编译器是无法使用的,其中传入的参数代表秒:

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

这段代码运行之后会出现什么现象呢?
很显然,打印出hello time之后,程序等待5s后再结束。
如果我们将代码改为:

#include<stdio.h>
   int main()
   {
     printf("hello time");
     sleep(5);                                                                                                 
     return 0;                                                                                          
   }               

注意两者的区别在于hello time后是否有换行符。
此时程序运行发生了变化:程序等待5s之后再发生打印。
为什么会出现这样的变化呢?下面来分析一下这个问题:
其实字符串在显示到显示器之前,会先被存放在缓冲区中,而没有显示到显示器。只有当遇到\n或者程序执行结束的时候,才会刷新显示器。在第二个程序中,字符串hello time会被先存放到缓冲区,等待遇到\n或者程序执行结束的时候刷新到显示器。
printf函数已经被执行了,只不过还没将结果显示到显示器而已,程序结束后刷新显示器才会在屏幕上打印。

(2)刷新显示器

如果我们不想加\n也不想等程序结束之后再刷新显示器,我们可以使用fflush来进行手动刷新。

#include<stdio.h>
     int main()
     {
       printf("hello time");
       fflush(stdout);                                                                                               
       sleep(5);
       return 0;
     }                   

我们使用man手册来查看一下fflush函数:
在这里插入图片描述
它的参数是一个流文件指针类型,其实C程序会默认打开三个输入输出流:stdin(键盘),stdout(显示器),stderr(错误显示器),在操作系统看来,其实它们都属于文件范围。我们要刷新显示器就需要向fflush中传入stdout。
此时这段代码和加入\n的那段代码的运行结果就相同了。

(3)倒计时

使用\n来刷新显示器的坏处在于,刷新后会发生换行操作。如果我们想设计一个倒计时的程序,执行结果可能就是这样的:
10
9
8

此时使用fflush(stdout)来刷新显示器的优势就被显示了出来。

#include<stdio.h>
     int main()
     {
       int i=0;
       for(i=10;i>=0;i--)
     {                                                                                                         
       printf("%d\r",i);
       fflush(stdout);
       sleep(1);
    }
      return 0;
    }

注意其中的\r表示回显,即将光标放回行初位置。但是我们发现打印倒计时的时候,10后面跟的0还一直在,9并没有将其进行覆盖。这是因为凡是显示在显示器或者在键盘读取的都是字符。我们的显示器和键盘都是字符设备,因此9只能覆盖一个字符,即10中的1。
我们可以通过预留空间来解决这一问题。

printf("%2d\r",i);

当打印的字符不足两个的时候,前面会使用空格来占位,刷新了10中的1。

(4)进度条

有了如上基础,我们就可以来实现一个简单的进度条了。

#include<stdio.h> 
#include<string.h> 
#include<unistd.h> 
#define NUM 100 
int main()    
{    
  char bar[NUM+1];    
  memset(bar,'\0',sizeof(bar));    
  int i=0;    
  const char* label="|/-\\";    
  while(i<=100)    
  {    
    printf("[%-100s][%3d%%][%c]\r",bar,i,label[i%4]);    
    fflush(stdout);                                                                                             
    bar[i]='#';    
    i++;    
   usleep(50000);    
  }    
  return 0;    
}    

我们可以根据个人的喜好来进行进度条内容的控制,这里我显示了进度条的进度的百分比,注意使用了usleep函数进行控制,与sleep不同的是,usleep传入的是微秒。

5.总结

本节我们了解了make和makefile的使用,偷偷说,博主刚开始也没有觉得make和makefile工具的实用之处,当我将test.c频繁地进行更改编译的时候,我发现make是真的香!如果没有make是不是还有些gcc那一长串?而在makefile的依赖方法中写一次就足够了。生成可执行的时候直接make。
我们还了解了进度条小程序,以及使用fflush函数来刷新显示器,在不想换行的情况下使用fflush来刷新缓冲区还是很实用的。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/33815.html

(0)
上一篇 2023-12-24 09:33
下一篇 2023-12-24 20:00

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信