目录
make/makefile
- 会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒
- ⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀ 系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄ 于进⾏更复杂的功能操作
- makefile带来的好处就是⸺“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全 ⾃动编译,极⼤的提⾼了软件开发的效率。
- make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这 个命令,⽐如:Delphi的make,VisualC++的nmake,Linux下GNU的make。可⻅,makefile 都成为了⼀种在⼯程⽅⾯的编译⽅法。
- make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。
基本用法:
当我们写了多个源代码,为了方便,我们可以新建一个makefile文件。
⼀般我们这种clean的⽬标⽂件,我们将它设置为伪目标,用 .PHONY修饰,伪目标的特性是总是被执行。
解释:
看一个现象:
我们加上 .PHONY修饰就可以连续执行了:
为什么加上.PHONY修饰就能连续执行?
因为 .PHONY修饰就是总是被执行生成目标文件。
而内容变了,往往属性就变了(大小变了,内容修改时间也变了),而属性变了内容不一定变!
演示:
这个时间和 .PHONY 有什么关系呢?
加了.PHONY修饰就是总是被执行,没加就是总是不被执行,也就是说就是没加.PHONY导致了不能连续生成可执行文件。
原理:
这个时间就是它们的Modify时间,.PHONY的作用就是忽略源文件和可执行文件的时间对比。
我们知道touch有新建文件的意思,但还有刷新文件时间的意思,我们刷新一下,就可以连续生成可执行文件了。
继续看:
但是我们一般不这样写,我们一般直接写一行就行,这只是推导。
扩展语法
我们每次写得时候,发现make一下,命令行都会回显,我们怎么样才能不回显?
加上@即可!
如果有多个源文件,makefile怎么写呢?
应用---进度条
缓冲区
我们知道\n是换行,而回车是\r(光标回到当前行的起始位置,而不清除原始数据,如果新数据来到,只覆盖而已),换行回车\n\r。
我们键盘上的回车键其实就是我们这里的回车换行键。
我们简单用printf函数打印一下,发现\n就能立马打印,而如果不加\n,就会发现不会立马打印。
只是不好观察。
我们可以借助sleep函数(秒)和usleep函数(微秒)观察!头文件:unistd.h
往显示器中打印时,首先进入缓冲区,然后程序结束再将缓冲区中的数据写入到显示器,所以\r不能立马显示出来,而\n能,因为\n是换行了,\n是行刷新。
显示器其实是字符设备,它只认识字符,我们写入的任何数据,在它看来都是字符!
我们写一个简单的倒计时:
加深理解 \r 和缓冲区:
printf的底层其实就是fprintf函数。
所以我们这样写:
我们来尝试写一个进度条:
#define NUM 101
#define STYLE '#'
void process_v1()
{
char buffer[NUM];
memset(buffer,0,sizeof(buffer));
const char* lable="|\/-\|";
int len=strlen(lable);
int cnt=0;
while(cnt<=100)
{
printf("[%-100s][%d%%][%c]\r",buffer,cnt,lable[cnt%len]);
fflush(stdout);
buffer[cnt++]=STYLE;
usleep(50000);
}
printf("\n");
}
解释:
以起始和结尾着手分析更好,循环过程不好分析,也不需要分析!
进阶---进度条:
我们在真正下载的时候,有一个下载多少mb,我们可以做一个非常拟合的进度条!
代码:
double total=1024.0;
double speed=1.0;
void Download()
{
int current=0;
while(current<=total)
{
process_v2(total,current);
current+=speed;
usleep(3000);
}
printf("\ndownload %.2f Done!\n",total);
}
#define NUM 101
#define STYLE '#'
void process_v2(double total,double current)
{
char buffer[NUM];
memset(buffer,0,sizeof(buffer));
const char* lable="|-\|/";
int len=strlen(lable);
int num=(int)(current*100/total);
int i=0;
for(i=0;i<num;i++)
{
buffer[i]=STYLE;
}
static int cnt=0;
printf("[%-100s][%d%%][%c]\r",buffer,num,lable[cnt++]);
cnt%=len;
fflush(stdout);
}
git的使用
我们以gitee为例。
安装git
yum install git
创建项目
使用
.git文件是隐藏的本地仓库,存放着所有的修改记录。
克隆
我们使用需要先将远程仓库克隆到本地:
git clone [刚刚复制的链接]
注意:第一次提价需要gitee的用户名和密码!
提交到暂存区
git add [路径文件]
提交到本地仓库
git commit -m "......"
注意:双引号中必须要填写内容。
但是我们commit必须要告诉git我们是以什么身份(什么名字),提交记录的,以便后续能找到是谁提交的这个代码!
我们可以试试这两句代码来告诉我们是谁:
git config --global user.email "you@example.com" (你的gitee邮箱地址)
git config --global user.name "Your Name"
验证是否成功:
注意:邮箱地址最后和gitee邮箱地址一样,名字可以不一样!
然后再commit一下,继续输入你的gitee用户名和密码即可,即可提交本地仓库成功!
这两句代码就是全局配置。运行完之后,家目录下自动会有.gitconfig文件(~/.gitconfig)
打开我们看到:
仓库配置:
git config user.email "you@example.com" (你的gitee邮箱地址)
git config user.name "Your Name"
我们来介绍两种配置的方法:(重点全局配置)
仓库配置:仅对当前仓库起效(只有当前仓库不需要密码!)
只需要配置仓库目录下的 .git/config 文件。
全局配置:对当前用户的所有仓库都起效!
只需要配置家目录下的.gitconfig文件。
我们主要介绍全局配置(提交到远程仓库也是讲全局配置)。
提交到远程仓库
提交需要gitee用户名和密码,我们可以配置免密码提交。
在家目录下,创建.git-credentials文件。
打开.git-credentials文件,输入:
https://{username}:{passward}@gitee.com
然后再执行命令:
git config --global credential.helper store
就会发现.gitconfig文件多了内容:
再重新执行push命令,即可成功!
指令:
git status:查看当前提交状态
git log:查看提交日志
总结:
当本地仓库和远程仓库内容不统一时,我们去提交,就会冲突,所以我们需要git pull 执行一下,将
本地仓库更新一下,保证远程仓库和本地仓库一致!
当然我们可以多次add和commit到本地仓库,最后一起提交到远程仓库!
gdb/cgdb
我们写了代码,肯定时不时的会发生错误,所以我们需要调试器----gdb/cgdb
下载:
yum install -y gdb
yum install -y cgdb
我们可以使用gdb,也能使用cgdb,一般使用cgdb,因为gdb在我们调试的时候,我们并不能看到我们的代码,比较难用,而cgdb能!
图:
一下我们都使用cgdb作为演示!
使用
预备
我们知道程序的发行有两种:debug模式和release模式,而Linux gcc/g++出来的二进制程序,默认时release模式。
注意:只有在debug模式下形成的可执行文件才能调试!
所以我们在形成可执行文件指令时,需要加上-g指令。
如:
gcc/g++ dst.c -o src -g
注意:gdb/cgdb 后面接形成后的可执行文件!而不是接源文件!
如:
gdb/cgdb src
实操
基本指令:
退出:ctrl+d 或 q 或 quit。
l +行号:显示源代码,每次列出10行,列举出来后,再按一次回车,可以看到全部代码。
如:
光标切屏:i 和 ESc 按键。
解释:
b 行数或 b dst.c:行数 :在某一行打断点。
info b:查看断点信息。
disable 断点编号:禁用此断点。
enable 断点编号:启用此段点。
d 断点编号:删除此断点。
b 函数名:在函数开头打断点,相当于在这个函数的第一行打了断点。
r:程序开始跑起来,跳到第一个断点处。当在调试中,想重新开始一遍,也可以按下 r ,回到第一个断点处。
n:(next)代表逐过程,相当于 vs 2022中的F10按钮。比如可以跳过函数。
s:(step)代表逐语句,相当于vs2022中的F11按钮。
如果想跳过一个循环呢?逐过程和逐语句都跳不过循环。
until:执行到函数中,可直接跳出循环。
until 行数:执行到指定行。
c:(continue)执行到下一个断点。
finish:直接跳出函数。
p 变量名:(print)查看变量名内容。
info locals:看当前函数的所有临时变量内容。
watch 变量名:执⾏时监视⼀个表达式(如变量)的值。如果监视的表达式在程序运⾏期间的值发⽣变化,GDB会暂 停程序的执⾏,并通知使⽤者。
set var 表达式:临时修改变量值,一般用来确定问题原因。
条件断点
b 行号 if 变量名==值:只有i等于某个值时,才触发这个断点,且只触发一次。
condition 断点编号 变量名==值:给某个断点新增条件。
这里不过多展示。
反汇编:
objdump -S src > src.s
将可执行文件反汇编成.s文件。
好了,我们下期见。