4.5自动构建make/Makefile

make是⼀条命令,Makefile是⼀个文件,两个搭配使用,完成项目自动化构建。

makefile带来的好处就是⸺“自动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全自动编译,极⼤的提高了软件开发的效率。

make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。

make命令和makefile文件——依赖关系和依赖方法

4.5.1基本使用

1示例

简述:创建Makefile/makefile文件,写入依赖关系和依赖方法,通过make命令执行。

touch test.c
touch Makefile

//test.c
#include<stdio.h>
int main()
{
	printf("hello world!\n");
	return 0;
}

//Makefile
test.exe:test.c            //依赖关系,test.exe依赖test.c
	gcc -o test.exe test.c //依赖方法,test.c形成test.exe

make
./test.c
2进一步解释
//Makefile
test.exe:test.c
	gcc -o test.exe test.c

test.exe是目标文件

test.c等是依赖文件列表

依赖关系,test.exe依赖test.c

依赖方法,test.c形成test.exe

二者共同构建可执行目标文件的语义。

形成可执行文件的过程:

//Makefile
test.exe:test.o
	gcc test.o -o test.exe
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后
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test.exe

make会解析Makefile文件,形成推导依赖关系的推导栈(依赖方法的集合)

依赖方法不存在就入栈,推导成功就执行对应命令出栈

(其实实现过程类似于函数递归的过程)

3实践
//构建
test.exe:test.c
	@gcc -o test.exe test.c
	//加上@会禁止命令回显

//清理
.PHONY:clean
clean:
	@rm -f test.exe test.i test.s test.o

PHONY:假的,伪造的

.PHONY:用来修饰目标文件是一个伪目标,.PHONY:修饰的伪目标总是被执行的。

总是被执行的原理是什么?为什么gcc无法二次编译旧代码?

首先我们知道可执行文件是文件,拥有修改时间的属性,而源文件是否需要被重新编译,需要判断源文件修改时间和可执行文件的修改时间谁更新,更接近当前时间;而**.PHONY:可以让gcc或者对应的命令,忽略MOD时间对比新旧。**

关于文件时间:

stat 文件名在这里插入图片描述

(简记ACM时间)

access:文件访问时间

modify:文件内容更改时间

change:文件属性更改时间

了解:

更改文件内容不仅会更新文件内容的时间,还会更新文件属性的时间,因为Mod时间也属于文件属性。

在对文件进行访问时,并不是每次都会更新access时间。原因是:查看文件会更新时间,接着更新文件属性,然后过程会刷新到磁盘(磁盘属于外设,效率比较低),如果每当文件被访问时都会更新时间进行以上过程,会导致OS整体的效率低下。所以为了提高效率会设置为当访问次数达到一定次数时(10,20次等)才会更新时间。

结论

1,依赖关系必须存在,依赖文件列表可以为空

2,依赖方法可以是任何shell命令

3,clean目标,只是利用make的自动推导能力,执行了rm命令,在构建工程的视角,拦起来就是清理项目,即删除不需要的临时建立的文件。

4,make命令,后面可以跟目标名,之后解析推导对应的依赖关系和依赖方法,

默认情况下make只会推导一条完整的推导链路,默认只会推导第一个依赖关系对应的推导链。

5,源文件发生更改才会重新编译!

4最佳实践
//其实类似于宏替换
BIN=test.exe
SRC=test.c

$(BIN):$(SRC)
	@echo "编译开始"
	@gcc -o $@ $^      
	@echo "编译完成"
.PHONY:clean
clean:
	@echo "清理开始..."
	@rm -f $(BIN)
	@echo "清理完成..."

问题:如果有多个文件,怎么高效编译?

touch src{1..100}.c该命令意义是创建100个.c后缀文件命名为src1.c、src14.c等

rm src{1..100}.c删除

BIN=test.exe
//SRC=$(shell ls *.c)
SRC=$(wildcard *c)	//wildcard函数获取当前目录下的源文件
OBJ=$(SRC:.c=.o)	//.c文件全部替换为.o文件
CC=gcc				//编译器
Echo=echo
Rm=rm -f

$(BIN):$(OBJ)	//.o文件链接形成可执行文件
	@$(CC) -o $@ $^
%.o:%.c
	@$(CC) -c $<
	
.PHONY:clean
clean:
	@$(Rm) $(OBJ) $(BIN)

.PHONY:test
test:
	@$(Echo) "------"
	@$(Echo) $(SRC) 
	@$(Echo) "------"
	@$(Echo) $(OBJ) 
	@$(Echo) "------"

至此我们就完成了基本可以通用的Makefile文件。

4.6练习:进度条

4.6.1倒计时

回车和换行是两个不同的操作。

回车\r 换行\n

printf("hello world\r\n");
sleep(2);
//现象:先打印后休眠

printf("hello world");
sleep(2);
//现象:先休眠后打印

在sleep期间,print函数已经执行完了,hello world此时被缓存到内存空间中了,没有打印到显示器,等到程序结束时,会自动刷新。刷新方式,以行为方式刷新,\r\n或者\n。

到此我们就可以实现一个简单的倒计时

#include<stdio.h>

#define COUNT 15

int main()
{
  int cnt = COUNT;
  while(cnt >= 0)
  {
    printf("%-2d\r",cnt);
    fflush(stdout);  
    sleep(1);
    --cnt;
  }
  printf("\n");
  
  return 0;
}

4.6.2进度条version1

首先展示一下最后的效果图:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现的主要步骤与刚才练习的倒计时是相似的,主要是回车以及刷新缓冲区的操作。

cat progress.h

#pragma once
#include<stdio.h>
void progress_v1();

cat progress.c

#include"progress.h"
#include<string.h>
#include<unistd.h>

#define NUM 101
#define STYLE '-'
const char *lable="|/-\\";

void progress_v1()
{
  char buffer[NUM];
  memset(buffer,0,sizeof(buffer));
  
  int cnt = 0;
  while(cnt <= 100)
  {
    int len = strlen(lable);

    printf("[%-100s][%d%%][%c]\r",buffer,cnt,lable[cnt%len]);
    fflush(stdout);
    buffer[cnt] = STYLE;
    ++cnt;
    usleep(500000);
  }
}

cat main.c

#include"progress.h"

int main()
{
  progress_v1();
  
  return 0;
}

4.6.2进度条version2

cat progress.h

#include<stdio.h>
#include<string.h>
#include<unistd.h>

void progress(double total, double current);

cat progress.c

#include"progress.h"

#define NUM 101
#define STYLE '-'

void progress(double total, double current)
{
  if(current > total)
        current = total;
  
  char buffer[NUM];
  memset(buffer,0,sizeof(buffer));
  
  const char *lable = "|/-\\";
  
  int len = strlen(lable);
  static int index = 0;
  
  double rate = current / total * 100;
  int num = (int)(rate);

  int i = 0;
  for(; i < num; i++)
  {
    buffer[i] = STYLE;
  }

  printf("[%-100s][%.1lf%%][%c]\r", buffer, rate, lable[index++]);
  index %= len;
  fflush(stdout);

  if(current >= total)
  {
    //printf("\n");
    printf("[%-100s][%.1lf%%][%c]\r", buffer, 100.0, lable[index++]);
  }
}

cat main.c

#include"progress.h"

double total = 800.0;
double speed = 3.4;

void DownLoad()
{
  double current = 0;
  while(current <= total)
  {
    progress(total, current);
  
    usleep(3000);
    current += speed;
  }
  // 循环结束后,确保显示100%
  progress(total, total);
  printf("\ndownload %.2lfMB Done\n", current);
}

int main()
{
  DownLoad();
  //progress(total, current);

  return 0;
}

4.7版本控制器Git

Git可以控制电脑上所有格式的文件,例如doc、excel、dwg、dgn、rvt等等。对于我们开发人员来说,Git最重要的就是可以帮助我们管理软件开发项目中的源代码文件!

4.7.1git操作

1操作一次,以后不愁

安装:yum install -y git

查看版本:git --version

初始化本地仓库:git init

注意:千万不要在一个git仓库里面再执行 git init,这会创建嵌套的仓库,会导致各种问题。

配置本地仓库:name和email

git config user.name “用户名”

git config user.email “邮箱名”

免密码push:执行git config --global credential.helper store

之后进行一次push操作,输入用户名和密码即可

查看配置项:git config -l

删除配置项:git config --unset user.name

所有的git仓库设置git config --global user.name "用户名"

2经典"三件套"

添加指定文件:git add 文件名

添加所有文件到暂存区:git add .

提交本地:git commit -m "提交日志"

同步远端:git push

3常用

查看历史提交记录:git log

git log --pretty=oneline

查看git状态:git status

远端同步到本地:git pull

git pull的本质:提交自己的代码之前,必须把别人历史提交的代码同步到本地。

克隆现有的远程仓库到本地:git clone 远程仓库URL

查看远程仓库名:git remote

查看远程仓库详细信息:git remote -v

git diff 命令用于比较 Git 管理的代码在不同版本、不同区域之间的差异。它会以行-by-行的形式告诉你哪些内容被添加(+)、删减(-)或修改了。

4版本回退
工作区 暂存区 版本库 指令
是否回退 git reset --soft
是否回退 git reset --mixed(默认选项)
是否回退 git reset --hard(谨慎使用)

原因:hard选项会回退所有版本包括工作区未提交的代码,所以需要谨慎使用!!

将工作区回退到最近一次add的版本:git checkout -- 文件名

工作区 暂存区 版本库 解决方式
xxx code 1,手动撤销(麻烦易出错)2,git checkout – file_name
xxx code xxx code
xxx code xxx code xxx code

4.7.2小结

1,git进行版本控制时,通过同步记录"变化"信息来进行版本控制,可以减少空间的消耗。

2,git是一个去中心化的、分布式的版本控制器,但是使用时还是以中心化的模式为主。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐