一个工程中的源文件多不技计数,其按其按类型、功能、模块分别放在若干个目录中,makefile
定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
makefile
带来的好处就是一一“自动化编译”,一旦写好,只需要一个make
命令,整个工程完全自动编译,极大提高了软件开发的效率。
make
是一个命令工具,是一个解释makefile
中指令的命令工具,一般来说,大多数IDE
都说有这个指令,比如:Delphi的make
,visual C++
的nmake
,Linux
下GNU
的make
。可见,makefile
都成为了一种在工程方面的编译方法。
make
是一条指令,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
中,有几个常用的命令符号和特殊规则,它们用于定义和管理构建过程。以下是一些常用的命令符号和其用途:
命令符号 @
用法:@
符号用于抑制命令的回显。通常,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
的基础,掌握它们可以帮助你更高效地管理构建过程。
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命令。那么,
项目清理
依赖关系:右侧的依赖文件,一个一个一个的交给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第一个偏系统的一个样例程序︰进度条
0x0D
) 作为行末标记。换行 (Line Feed, LF): - 换行将光标移动到下一行
0x0A
) 作为行末标记。回车+换行 (CR+LF):
0x0D 0x0A
) 作为行末标记。新起一行:本质:先回车,在换行
\r ln
\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)
期间:
所以,在程序执行 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");
」
################################################################################################
version 2
process.h
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
//version 1
//void Process();
//version 2
void FlushProcess(double total,double current);
process.c
#include "process.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define NUM 101
#define STYLE '='
#define POINT '.'
#define SPACE ' '
const int pnum = 6;
void FlushProcess(double total ,double current)
{
//1,更新当前进度条百分比
double rate = (current / total)*100;
//printf("%.1lf%%\r",rate);
// fflush(stdout);
//2,更新进度条主体
char bar[NUM];//我们认为1%更新一个=
memset(bar,'\0',sizeof(bar));
int i;
for(i = 0; i < (int)rate; ++i)
{
bar[i] = STYLE;
}
//3,更新旋转光标或者其他风格
static int num = 0;
num++;
num %=pnum;
char points[pnum+1];
memset(points,'\0',sizeof(points));
int j ;
for(j = 0; j < pnum; ++j)
{
if(j < num) points[j] = POINT;
else points[j] = SPACE;
}
printf("[%-100s][%.1lf%%][%s]\r",bar,rate,points);
usleep(10000);
fflush(stdout);
}
main.c
#include "process.h"
#include <stdlib.h>
typedef void(*flush_t)(double total,double current);
//这是一个动态刷新的函数指针类型
const int base = 100;
double total = 2048.0;//2048mb
double once = 0.1;//0.5mb
//进度条调度方式
void download(flush_t f)
{
double current = 0.0;
while(current < total)
{
//
int r = rand() % base +1;
double speed = r * once;
current += speed;
if(current >= total)
{
current = total;
}
usleep(10000);
f(total,current);
f(total,current);
}
}
int main()
{
srand(time(NULL));
download(FlushProcess);
printf("\n");
return 0;
}
make name
make makfile
在执行gcc命令的时候,如果发生了语法错误,就会终止推导过程make
解释makefile的时候,是会自动推导的。一直推导,推导过程,不执行依赖方法。直到推导到有依赖文件存在,然后在逆向的执行所有的依赖方法make
默认只形成一个可执行程序