
文章目录
- 📝前言
- 🌠 Makefile 格式
- 🌉Makefile命令符号
 
- 🌠makefile/make基本原理
- 🌉总和小案例
 
- 🌠进度条代码
- 🚩总结
📝前言
一个工程中的源文件多不技计数,其按其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
makefile带来的好处就是一一“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大提高了软件开发的效率。
make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数IDE都说有这个指令,比如:Delphi的make,visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
make是一条指令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
🌠 Makefile 格式
Makefile 由一系列规则组成,每个规则的格式如下:
target: dependencies
    commands
- target: 要生成的文件或目标。可以是文件名,也可以是一个标签(label)。
- dependencies: 生成- target所需的文件或目标,用空格分隔。
- commands: 生成- target所需执行的命令,每行一条命令,必须以制表符(- \t)开头。
例如:
 
Makefile 语法
- 伪目标:
 伪目标不对应任何文件,通常用于定义常用的构建任务。.PHONY: clean clean: # commands

make 命令
常用的 make 命令有:
- make: 构建- Makefile中的默认目标。
- make target: 构建指定的目标。
- make clean: 清理构建产生的中间文件。
- make all: 构建 Makefile 中的所有目标。
- make -n: 显示执行命令,但不实际执行。
- make -j <number>: 并行构建,指定同时执行的命令数量。
使用make命令,可以直接执行Makefile的文件命令
 
 但是,当我再次执行make命令,这里的proc的文件无法再次执行:
 
这个问题是因为:
.PHONY是让目标文件,对应方法,总是被执行。(让依赖方法,忽略时间对比),这里的rm-f命令本来就不关心时间,只要make,这个指令就会执行,所以我们把.PHONY加在这里,无法看出效果。
 
 把伪目标加在前面,让他忽略时间对比,仍然执行目标文件指令:gcc -o proc proc.c:如图:
 
 make执行
 
这里提及到了时间,有一问题:对于源文件和可执行出程序,有时候需要重新编译有时候不需要?为什么?
 对于源文件和可执行程序,可以说都是文件,而文件 = 内容 + 属性------》而属性其中包含了文件的时间:
stat命令:
 stat 命令用于显示文件或文件系统的状态信息。它可以输出文件的各种属性,如文件类型、权限、所有者、大小、访问和修改时间等。
stat [OPTION]... FILE... 

 
🌉Makefile命令符号
在 Makefile 中,有几个常用的命令符号和特殊规则,它们用于定义和管理构建过程。以下是一些常用的命令符号和其用途:
- 命令符号 @- 用法:@符号用于抑制命令的回显。通常,make会在执行每一条命令时打印命令本身。使用@符号可以让make只输出命令的结果,而不输出命令行。
- 示例:
 
- 用法:
#目标文件 依赖关系列表
 2 .PHONY:proc
 3 proc:proc.c
 4 @echo “hello make”
 5 @echo “hello make”
 6 @echo “hello make”
 7 @echo “hello make”
 8
 9 # gcc -o proc proc.c
 10 #.PHONY:clean
 11 clean:
 12 rm -f proc
 ```

-  伪目标 .PHONY- 用法:.PHONY用于声明伪目标。伪目标不是实际存在的文件,而是用于执行特定的命令。声明伪目标可以防止与实际文件名冲突,确保每次make都执行相关命令。
- 示例:.PHONY: clean clean: rm -f *.o my_program
 
- 用法:
-  变量赋值 - 用法:Makefile支持变量赋值,用于简化和重用配置。变量可以在Makefile中定义并在规则中使用。
- 示例:CC = gcc CFLAGS = -Wall -g TARGET = my_program $(TARGET): main.o utils.o $(CC) $(CFLAGS) -o $(TARGET) main.o utils.o
 
- 用法:
-  自动变量 
- 用法:自动变量在规则中使用,能够引用当前目标、依赖文件等。
- 常见自动变量: 
  - $@:表示规则中的目标文件。
- $<:表示第一个依赖文件。
- $^:表示所有的依赖文件(去重)。
- $?:表示比目标文件更新的所有依赖文件。
 
- 示例:program: main.o utils.o $(CC) -o $@ $^ main.o: main.c $(CC) -c $< utils.o: utils.c $(CC) -c $<
- 条件判断
- 用法:可以使用条件判断来决定是否执行某些规则或命令。
- 示例:ifeq ($(DEBUG), 1) CFLAGS += -g else CFLAGS += -O2 endif
-  模式规则 - 用法:模式规则允许你定义一类规则,从而简化多个类似文件的编译过程。
- 示例:%.o: %.c $(CC) -c $(CFLAGS) $< -o $@
 
-  内置规则 - 用法:make提供了一些内置规则来处理常见的文件生成任务,例如编译.c文件到.o文件。如果不定义这些规则,make会尝试使用默认规则。
- 示例:# 默认规则 %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
 
- 用法:
-  包括其他 Makefile 
- 用法:可以使用 include指令来包含其他Makefile文件,以实现配置的模块化。
- 示例:include config.mk
这些符号和规则是编写和维护 Makefile 的基础,掌握它们可以帮助你更高效地管理构建过程。
 
🌠makefile/make基本原理
- make解释makefile的时候,是会自动推导的。一直推导,推导过程,不执行依赖方法。直到推导到有依赖文件存在,然后在逆向的执行所有的依赖方法
Makefile  ⮀                                                                                                         ⮂⮂ buffers 
  1 #目标文件   依赖关系列表
  2 .PHONY:proc clean
  3 proc:proc.o
  4     gcc proc.o -o proc
  5 proc.o:proc.s
  6     gcc -c proc.s -o proc.o
  7 proc.s:proc.i
  8     gcc -S proc.i -o proc.s
  9 proc.i:proc.c
 10     gcc -E proc.c -o proc.i
 11 
 12 .PHONY:clean clean-all
 13 clean:
 14   rm -f proc.i proc.s proc.o proc
 15 clean-all:
 16   rm -f proc  

依赖关系
上面的文件 hello ,它依赖 hell.o
hello.o, 它依赖 hello.s
hello.s , 它依赖 hello.i
hello.i , 它依赖 hello.c
%是makefile语法中的通配符
%.c:当前目录下所有的.c文件,展开到依赖列表中
依赖方法
gcc hello.* -option hello.* ,就是与之对应的依赖关系
原理
 make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么,
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“hello”这个文件,
 并把这个文件作为最终的目标文件。
- 如果hello文件不存在,或是hello所依赖的后面的hello.o文件的文件修改时间要比hello这个文件新(可
 以用 touch 测试),那么,他就会执行后面所定义的命令来生成hello这个文件。
- 如果hello所依赖的hello.o文件不存在,那么make会在当前文件中找目标为hello.o文件的依赖性,如果
 找到则再根据那一个规则生成hello.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成 hello.o 文件,然后再用 hello.o 文件声明
 make的终极任务,也就是执行文件hello了。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文
 件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,
 而对于所定义的命令的错误,或是编译不成功,make根本不理。
- make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,
 我就不工作啦。
项目清理
- 工程是需要被清理的
- 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
- 但是一般我们这种clean的目标文件,我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。
- 可以将我们的 hello 目标文件声明成伪目标,测试一下。
依赖关系:右侧的依赖文件,一个一个一个的交给gcc -c选项,形成同名的.o文件
Makefile :                                                                                                      
  1 #目标文件   依赖关系列表
  2 
  3 proc:proc.o
  4     gcc proc.o -o proc
  5 %.o:%.c
  6   gcc -c $<                                                                                                                      
  7 
  8 .PHONY:clean
  9 clean:
 10   rm -f proc.o proc
 11   


Makefile+ :                                                                                                  
  1 bin=proc
  2 src=proc.o
  3 $(bin):$(src)
  4     gcc %^ -o $@
  5 %.o:%.c
  6   gcc -c $<
  7                                                                                                                                  
  8 .PHONY:clean
  9 clean:
 10   rm -f proc.o proc
 11   
原型为:
proc1.c proc2.c proc3.c
%.o:proc1.c proc2.c proc3.c
gcc -c proc1.c -o proc1.o
以下同理:
gcc -c proc2.c
gcc -c proc3.c

🌉总和小案例
我们将使用gcc/g++, vim, make/makefile 进行制作一 linux第一个偏系统的一个样例程序︰进度条
- 回车与换行
 回车 (Carriage Return, CR): - 回车将光标移动到行首
- 回车是一个控制字符:将光标移动到当前行的开头。
- 在早期的打字机和电传打字机上,回车会使打字头或打字轮返回到行首。
- 在 macOS 和早期的 Mac OS 系统中,文本文件使用回车 (ASCII 编码 0x0D) 作为行末标记。
换行 (Line Feed, LF): - 换行将光标移动到下一行
- 换行是一个控制字符:将光标移动到下一行。
- 在早期的打字机和电传打印机上,换行会使纸张向下移动一行。
- 在 Unix/Linux 系统中,文本文件使用换行 (ASCII 编码 0x0A) 作为行末标记。
回车+换行 (CR+LF):
- 在 Windows 系统中,文本文件使用回车+换行 (ASCII 编码 0x0D 0x0A) 作为行末标记。
- 这是为了向后兼容早期的打字机和电传打印机,既移动光标到行首,又移动到下一行。
新起一行:本质:先回车,在换行
              \r      ln

- 缓冲区刷新:
 在 Linux 系统中,\r和\n在刷新缓冲区方面有以下区别:
\n (换行符):
- 当遇到 \n时,Linux 系统会将缓冲区中的数据立即刷新到输出设备(如终端或文件)。
- 这是因为 \n在 Linux 中被视为行末标记,表示一个完整的行已经写入。
- 当缓冲区中有 \n时,系统会立即将缓冲区中的数据刷新到输出设备,以确保数据能够及时显示。
\r (回车符):
- 当遇到 \r时,Linux 系统不会立即刷新缓冲区。
- \r只是将光标移动到当前行的开头,并不表示一个完整的行已经写入。
- 缓冲区中的数据会一直保留在缓冲区中,直到遇到 \n或者缓冲区被手动刷新。
手动刷新缓冲区:
- 除了遇到 \n时自动刷新,您也可以手动刷新缓冲区。
- 常见的手动刷新方式包括调用 fflush()函数或者关闭文件/终端。
- 手动刷新可以确保缓冲区中的数据立即被写入输出设备,而不需要等待 \n的出现。
总结:在 Linux 系统中,\n 会触发缓冲区的自动刷新,而 \r 不会。如果需要立即将缓冲区中的数据写入输出设备,可以手动调用 fflush() 或者关闭文件/终端。这样可以确保数据能够及时显示,而不需要等待 \n 的出现。
如:
 
 
 #include <stdio.h>
 #include <unistd.h>
 int main()
 {
   printf("Hello world!");
   sleep(2);
 
   return 0;                                                                                                                    
 }  
-  首先会执行 printf("Hello world!");语句,将字符串 “Hello world!” 输出到标准输出(通常是终端)。
-  然后会执行 sleep(2);语句,程序会暂停 2 秒钟。
在程序执行 sleep(2) 期间:
-  “Hello world!” 字符串已经被输出到标准输出了,此时它已经在终端上显示出来了。 
-  程序进入睡眠状态,不会执行任何其他操作,只是等待 2 秒钟后继续执行后续的语句。 
所以,在程序执行 sleep(2) 期间,“Hello world!” 字符串已经被输出到终端上了,不会在缓冲区中等待。
这是因为 printf() 函数在 Linux 系统上默认是行缓冲的,也就是说当遇到换行符 \n 时,才会将缓冲区中的数据刷新到输出设备(终端)。在这个例子中,由于没有换行符,printf() 会立即将数据刷新到终端上。
所以,在程序执行 sleep(2) 期间,“Hello world!” 字符串已经显示在终端上了,不会在缓冲区中等待。
倒数:
Count.c
                                                                                            ⮂⮂ buffers 
 #include <stdio.h>
 #include <unistd.h>
 int main()
 {
     int count = 10;
    while(count >= 0)
     {
       printf("%d\r",count);//\r回车,但是没有换行也就咩有 刷新
         fflush(stdout);                                                                                                          
       count--;
       sleep(1);
     }
     printf("\r\n");
 }
显示器是一个一个刷新的,因此需要 printf(“%-2d\r”,count);整两个内存区域一起进缓冲区,一起刷新。
 
 
🌠进度条代码
版本1:
 main.c:
#include "process.h"                                                                                                                                                                                   
int main()                                                                                         
{                                                                                         
   Process();                                                                                         
    return 0;                                                                                                                  
} 
process.h:
#include <stdio.h>    
#include <string.h>    
#include <time.h>    
#include <unistd.h>    
    
//version 1    
void Process();  
process.c:
#include "process.h"
#define NUM 100
#define STYLE '='
//version1
void Process()
{
    const char * lable = "|/-\\";
    int len = strlen(lable);
    char bar[NUM + 1];
    memset(bar,'\0',sizeof(bar));
    int cnt = 0;
    while(cnt <= NUM)
    {
        printf("[%-100s][%d%%][%c]\r",bar,cnt,lable[cnt%len]);    
        fflush(stdout);       
        bar[cnt] = STYLE;                   
        cnt++;             
        if(cnt == NUM)    
        {                                                              
            bar[cnt-1] = STYLE;                                      
            printf("[%-100s][%d%%][%c]\r",bar,cnt,lable[cnt%len]);    
            break;                                                                                                                          
        }                                                    
        bar[cnt] = '>';                           
        usleep(50000);                                                                                       
    }                     
    printf("\r\n"); 
」

🚩总结
- makefile文件,会被make从上到下开始扫描,第一个目标名,是缺省要形成的。如果我们想执行其他组的依赖关系和依赖方法,make name
- make makfile在执行gcc命令的时候,如果发生了语法错误,就会终止推导过程
- make解释makefile的时候,是会自动推导的。一直推导,推导过程,不执行依赖方法。直到推导到有依赖文件存在,然后在逆向的执行所有的依赖方法
- make默认只形成一个可执行程序



![2024Mysql And Redis基础与进阶操作系列(1)作者——LJS[含MySQL的下载、安装、配置详解步骤及报错对应解决方法]](https://i-blog.csdnimg.cn/direct/46749a9ae2bf431688491b937adcbbb2.png)
















