【Linux】make/makefile/gdb调试技巧/进度条小程序
目录
一、sudo提权:
二、自动化构建工具make与Makefile
makefile:
make:
是否重新执行make:
伪目标:
三、进度条小程序:
四、Linux调试器gdb:
1.、前景提要:
2、进入与退出:
3、运行代码与打断点:
4、逐过程与逐语句:
5、监视:
6、其他:
一、sudo提权:
Linux的权限是指对文件或目录的读、写、执行的控制,Linux的权限分为三个级别:文件所有者、文件所属组、其他人,Linux下还有超级用户(root)和普通用户的区别,超级用户可以做任何事情,普通用户受到限制,所以每次当我们在使用普通账号的时候,如果要安装软件,或者搞啥需要root权限的时候,可以直接切换账号,但是这样比较麻烦,有种“杀鸡用牛刀”的感觉了。
为了解决这种问题,就有一种指令sudo,能够帮助我们从普通账号的权限暂时升级拥有root的权限,这是借助了root的身份,所以创建出来的文件之类的拥有者就是root。
但是尽管安装了sudo这个指令,但是最开始是用不好的,需要让root将当前账号加入白名单
首先使用root账号,在根目录下,进入etc目录,找到sudoers文件,并打开它
如下,打开后在root账号下增加自己的用户名,后面将root的后面cv下来,即可
这样,在以后遇到普通用户被拒绝的情况下,只要进行sudo提权,就可以暂时使用root的身份了,在执行sudo提权后的代码需要输入的密码是当前普通账号的密码。
二、自动化构建工具make与Makefile
在我们实际的操作中,如果想编译代码,就需要自己手动地输入g++/gcc ...... 这是很繁琐的,如果在大型的项目中,有许多文件,那么是不是一个一个地编译呢,这样不仅很麻烦,而且还有可能在删除的时候就会出错导致删除出错误的文件,那么为了解决这一问题,就引入了make与makefile
make是一个指令,makefile使一个文件,二者相互搭配使用,帮助项目完成自动化构建
makefile:
Makefile是一种脚本,定义了项目中各个文件之间的依赖关系和依赖方法(构建过程)
依赖关系:
依赖关系定义了一个目标是如何生成的,即目标文件需要哪些源文件和头文件,
如下,在这个目录下只有一个test.cpp文件,然后我需要编译形成一个mytest文件,就可以写成:
mytest:test.cpp 这样的形式,由于test.cpp文件的改变会影响mytest文件,所以说mytest文件依赖于test.c文件,这就是个依赖关系。
依赖方法:
借助上面的例子:
mytest依赖于test.cpp,而cpp.c通过 g++ -o mytest test.cpp 这个指令就可以得到mytest,那么mytest依赖于test.cpp的依赖方法就是g++ -o mytest test.cpp。
上述中,为了方便采用了两个组合字符:
$@ 这个就代表着:左边的内容
$^ 这个就代表着:右边的所有内容
make:
make命令会读取Makefile,按照其中的规则来自动化编译和链接。它是一个命令工具,用于解释Makefile中的指令。make会根据Makefile中的规则执行命令,完成项目的自动化构建
如上,当在Makefile文件中写入上述指令后,在编译就只需要输入一个make就可以自动完成编译,编译出一个可执行程序mytest,这样就可以直接运行了
原理:
1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件
2、如果找到,它会找文件中的第一个目标文件,在上面的例子中,他会找到“mytest”这个文件,并把这个文件作为最终的目标文件
3、如果mytest文件不存在,或是mytest所依赖的后面的test.cpp文件的文件修改时间要比mytest这个文件新,那么,他就会执行后面所定义的命令来生成mytest这个文件
4、如果mytest所依赖的test.cpp文件不存在,那么make会在当前文件中找目标为test.cpp文件的依赖性,如果找到则再根据那一个规则生成test.cpp文件
5、这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件
6、在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理
在进行多项目编译的时候,:后面跟着的就是一个项目中的各个源文件
是否重新执行make:
make会根据源文件和目标文件的新旧,判定是否需要重新执行依赖关系,进行编译。
首先了解一个指令:stat
用法:stat 文件名
作用:可以查看文件的状态信息,在这里我们先主要看Access,Modify,Change,
Access:是文件最近一次的访问时间,但是因为访问文件过于频繁,如果一直修改的话,导致运行速率会有所降低的,所以Access会过几次操作之后才会更新一次
Modify:是文件内容最近一次的修改时间,例如用vim,nano在文件内容中增删改
Change:是文件属性最近一次的修改时间,包括权限,大小等等
OK,了解上述文件的时间过后,现在来看看关于make的重新执行:
首先,当文件编译过后会有一个可执行文件,如下面的mytest,这个mytest所依赖的文件test.cpp也会有这三个时间,主要观察的是Modify的时间,如果可执行文件的最近Modify时间比它所依赖的test.cpp的时间要新就证明在编译后,test.cpp文件没有被修改,所以就不能够重新make编译
但是如果对test.cpp继续写入内容导致其Modify时间改变,那么此时的就能够重新编译了,这样就可以避免大量重复编译而消耗时间
但是如果我就想要make总是被执行呢?
伪目标:
用法 :.PHONY 文件名
当一个目标的依赖包含伪目标时,伪目标所定义的命令总是会被执行。
所以当我们对上述Makefile中进行增加伪目标
这样的话无论mytest和test.cpp文件的时间无论新旧都会执行对应的依赖方法,所以在外面无论test.cpp无论是否进行修改都能够一直进行编译
其原理就是在外面忽略了mytest文件,无论这个文件是否存在,都会把make当做指令强制运行
那么在项目清理中就可以伪目标
.PHONY用于标记目标为“伪目标”,即它不依赖于文件名。在运行make clean时,即使当前目录下有一个名为“clean”的文件,make也会忽略这个文件,把clean当作命令来执行
.PHONY:aa可以写成make aa
因为它不需要检查目标文件是否已经存在,直接执行相应的规则
补充:
如果用make的时不想看到我所执行的依赖方法,那么就在Makefile文件中的依赖方法前加@即可
三、进度条小程序:
前提知识:
1、在Linux中是以一行一行为缓冲区的,每当缓冲区被填满或者程序结束或者强制刷新才会在屏幕上打印缓冲区的东西。
强制刷新缓冲区可以用函数fflush(stdout)来进行缓冲区的刷新,
2、\r 与 \n,这是回车与换行,回车是将光标移动到本行的开头,换行是指从当前行向下移动一格
这样就可以每次写入东西的时候提前回车,这样的话就可以覆盖之前写的了
这样就可以先写一个简单的倒计时:
int main()
{ int count = 10;while(count>=0){printf("%-2d\r",count);fflush(stdout);sleep(1);count--;}printf("\n");return 0;
}
那么就可以用以上的知识写一个进度条出来了,如下所示。这样就是一个基本进度条的代码了,在main函数中调用,传参的是每次增加的时间 /单位微秒,例如在主函数中调用该函数并传50000
#define BODY '='
#define HEAD '>'
#define NUM 102
#define TOP 100void processBar(int speed)
{char bar[NUM];memset(bar,'\0',sizeof(bar));int count = 0;int len = strlen(lable);while(count <= TOP){printf("[%-100s][%d%%][%c]\r",bar,count,lable[count%len]);fflush(stdout);bar[count++] = BODY;if(count < 100 ) bar[count] = HEAD;usleep(speed);}printf("\n");
}
但是在实际上使用的时候并不是这样的,毕竟要使用者才知道此时加载了多少,所以就只需要一个打印当前进度的函数,然后循环在调用的时候传入当前进度,还可以加上颜色代码之类
#include<unistd.h>const char* lable="|/-\\";
char bar[NUM];#define GREEN "\033[0;32;32m"
#define NONE "\033[m"
#define RED "\033[41m"void processBar(int rate)
{if(rate < 0 || rate > 100) return;int len = strlen(lable);printf("\033[47m\033[30m\033[1m[%-100s][%d%%][%c]\r\033[0m",bar,rate,lable[rate%len]);fflush(stdout);bar[rate++] = BODY;if(rate < 100 ) bar[rate] = HEAD;
}void initBar()
{memset(bar,'\0',sizeof(bar));
}void download(callback_t cb)
{int total = 1000;int cur = 0;while(cur <= total){usleep(50000);//模拟下载时间int rate = cur*100/total;cb(rate);cur += 10;}printf("\n");
}int main()
{printf("download1:\n");download(processBar);initBar();printf("download2:\n");download(processBar);initBar();printf("download3:\n");download(processBar);initBar();printf("download4:\n");download(processBar);initBar();return 0;
}
四、Linux调试器gdb:
1.、前景提要:
首先,在操作系统中要先安装gdp调试工具,安装好之后就可以进行调试了,但是在这之前要了解release和dubug两个版本的不同:
release:这基本上算是完整品了,一般在程序猿的项目开发做完之后就可以以release的形式发布出去,在release中就没有调试信息的,毕竟发布出去的项目就已经经过多方面的测试出问题了就不需要在进行调试了
debug:这就是一般程序猿在开发的时候所使用的模式,里面包含着调试信息,这样才能经过gdb工具调试代码
如下使用了指令readelf,这个是能够 显示 elf 格式文件的信息
下面这个是看不到调试信息的,因为Linux中默认编译后的版本是release版本,想要变成debug版本就需要在编译中加上-g选项即可。
如下,在Makefile文件中的编译增加-g选项,这样的话重新编译之后在用readelf查看就可以找到debug的调试信息了。
2、进入与退出:
这样就在编译过后就可以调试可执行程序的文件,通过 gdb 可执行程序名,来进入调试状态
如上,当在左下角出现(gdb)并且没有出现报错就证明进入了调试模式
然后看自己所写的代码就是通过
list / l 行号 :这就是显示源代码,每次只显示10行,下一次使用就是接着上次的位置往下列
list / l 函数名:这就是列出某个函数的源代码
一般情况下,就输入 l 0即可
最后如果在调试过程中退出就可以是quit 或者简写q即可
3、运行代码与打断点:
在调试状态下,就可以直接输入指令run(简写r)来运行此代码,这个就是类似于 vs2022 的F5
关于断点:
语法:break(简写成b) 语句行就可以进行断点的打入,用delete(简写d)进行断点的删除,删除的话要的是断点的编号。
但是尽管打断点之后,在展示的过程中依然是看不到断点的样式的,这样的话就需要我们使用指令info b来观察断点的信息:(主要观察下面红框框中的信息即可)
编号是表示每一个断点的编号,通过编号的区别来将断点区分开,
中间断点的状态(是否开启)可以使用disable来进行关,用enable来进行开
当打断点之后就可以用 continue (简写c) 从一个断点运行到下一个断点
4、逐过程与逐语句:
在vs2022中,逐过程(F10)在遇到函数时,会把函数从整体上看做一条语句,会跳过当前函数块,
逐语句(F11)在遇到函数时,认为函数由多条语句构成,不会跳过当前函数块,会进入函数内部
在Linux的调试中,也有类似于上述的功能,
next(简写n)就是F10,step(简写s)就是F11
5、监视:
bt :查看当前程序堆栈的调用情况
p 变量名 : 查看指定变量的大小
display 变量名 : 一直查看指令变量的大小
6、其他:
finish 函数名,立刻执行完当前函数。
在进入很长的循环的时候,那么不能一句句语句去执行,所以就需要until来进行语句的跳转