本期我们来学习Linux的相关工具,这是我们未来经常使用的一些工具,是必须掌握的技能
目录
Linux 软件包管理器 yum
rzsz
Linux编辑器-vim使用
三种模式的切换
命令模式命令集
底行模式命令集
vim的配置
解决sudo的白名单问题
Linux编辑器—gcc/g++使用
Linux项目自动化构建工具-make/Makefifile
小程序——进度条
进度条进阶
v1版
v2版
v3版
git
三板斧
git的其他问题
gitignore
开源和删除
Linux调试器-gdb
指令汇总
Linux 软件包管理器 yum
我们在手机上下载软件时直接去应用商店或者百度之类的地方直接下载安装包安装即可,电脑上下载软件也是类似,那Linux是如何下载软件安装呢?
Linux安装软件的第一种方法就是源代码安装,比如动静态库等等,源代码安装是别人直接把源代码给你,你自己去看,自己去安装卸载,举个例子,我们用C语言写了一个通讯软件,我们要安装的话不仅要代码,还要C语言运行相关的各种软件,非常麻烦,所以我们不推荐
第二种安装是rpm安装,相当于我们在Linux在各种软件官方中下载rpm包,然后安装即可,不过这种方式我们也不推荐,因为他有很多依赖关系,比如我们下载了一个包,他就会报错,说是需要别的包来辅助,也是非常麻烦的
第三种安装就是yum安装,我们知道软件有各种版本,比如1.1,1.2,3.4等等,要安装一个软件,我们还要知道各种版本,但是有了yum,各种安装源代,版本,各种依赖关系,这些我们都不用去了解,因为无所谓,yum会出手,所以我们未来都会使用yum进行安装,yum就相当于我们的应用商店
yum安装可以需要网络,也可以不需要网络,但是我们是云服务器,所以是需要网络的,我们ping一下即可确认是否有网络,不过我们是云服务器,所以是百分比有网络的,不用担心
rzsz
我们有时候是可能需要在Windows和Linux下进行消息互传的
注意:
软件包名称 : 主版本号 . 次版本号 . 源程序发行号 - 软件包的发行号 . 主机平台 .cpu 架构 ."x86_64" 后缀表示 64 位系统的安装包 , "i686" 后缀表示 32 位系统安装包 . 选择包时要和系统匹配 ."el7" 表示操作系统发行版的版本 . "el7" 表示的是 centos7/redhat7. "el6" 表示centos6/redhat6.最后一列 , base 表示的是 " 软件源 " 的名称 , 类似于 " 小米应用商店 ", " 华为应用商店 " 这样的概念 .
安装软件是需要root权限的,所以我们切换为root账号,然后yum install 软件,即可安装,未来我们自己的账号加入白名单的话使用sudo也是可以的
yum 会自动找到都有哪些软件包需要下载 , 这时候敲 "y" 确认安装.出现 "complete" 字样 , 说明安装完成安装软件时由于需要向系统目录中写入内容, 一般需要 sudo 或者切到 root 账户下才能完成.yum安装软件只能一个装完了再装另一个. 正在yum安装一个软件的过程中, 如果再尝试用yum安装另外 一个软件, yum会报错.如果 yum 报错, 请自行百度.
有安装,那自然就有卸载,同样是一条指令,比如我们卸载lrzsz
yum remove lrzsz
我们在卸载软件时,系统会询问我们是否卸载,如果我们确定要卸载,并且不想被询问,加上-y选项即可,安装时同理,不想被询问加上-y选项
我们要安装哪些软件要看自己的需求,当我们需要的时候自然就会去了解我们要安装什么
那yum是怎么知道去哪里下载软件的呢?这个问题就和我们手机的应用商店怎么知道去哪里下载一样,所以yum一定会内置下载链接
我们在etc/yum.repos里就可以看到,这个base就很熟悉了
这个是可以打开的,打开我们就可以看到有各种链接,至于如何打开等我们学了vim就明白了
这个就叫做yum源,这里的是base的yum源,除了这个,还有软件官方的官方yum源和扩展源
大家可以试试安装一下这个
yum install -y epel-release
这个就是一个扩展源,然后我们
yum install -y sl
然后我们sl一下就会有好玩的事情
还有一个
yum install -y cowsay
还有很多有趣的东西,大家百度一下就可以找到
无论是我们的官方yum源还是扩展的都是可以配置的,一般yum源默认是国外的
有时候你的yum源访问会比较慢,可能需要更新yum源,更新yum源就是替换yum源文件,说白了就是找一个国内的yum源,替换一下就行,这个大家百度一下就会了,两三行代码,非常简单
Linux编辑器-vim使用
我们下面来学习在Linux中如何写代码,我们推荐使用的是vim
三种模式的切换
我们有一个test.c文件,我们直接vim test.c即可
进入之后,我们按键盘是好多都按不动的,因为我们刚进入vim,此时是默认模式,即命令模式
此时用户所有的输入都会被当作命令,一般不会作为文本输入
如果我们想写代码,我们需要把命令模式变为插入模式,变为插入模式有很多方法,我们先来最简单的,我们输入 i 即可
输入i后,左下角会变成这样,出现insert
然后此时我们就可以随意输入了
我们随便写一点代码,此时我们代码写完了,想要保存退出,我们先回退到命令模式,我们esc即可
要退出的话,我们需要进入到底行模式,从命令模式进入底行模式是 shift 加 ;
shift 加 ; 是冒号的意思,也就是我们输入冒号就可以了
此时左下角变为冒号
然后我们wq即可,w是单纯的保存,q是单纯的退出(不保存),所以wq就是保存加退出
此时我们cat一下,发现确实写入了
从底行模式到命令模式也是esc
我们不考虑插入模式和底行模式之间的切换
三者关系如上
命令模式到插入模式除了i,还可以a或者o
下面我们的重点是命令模式,我们要讲很多的命令,以下操作均处于命令模式
命令模式命令集
这个绿色的方块就是我们的光标,如果我们想把光标快速定位到文本的开始位置,即第一行
我们输入gg即可
我们想要把光标定位到整个文本的结尾,shift g 即可,也就是 G
如果我们想定义到任意一行,比如第3行,第5行,第7行,我们 n shift g 即可,其中n为数字
比如我这里就是 3+shift+g,也就是 nG
如果我们想要把一行代码进行复制,比如将printf复制10次
我们 yy p即可,yy是复制光标所在行,p是粘贴到光标所在的下一行,如果复制10行,那么就是yy 10 p,也就是np,我们复制1w行,10w行也是可以的
既然p可以np,yy也是可以nyy的
比如这里我就是在光标所在行2 yy,然后在结尾处p
我们还有一个经常使用的操作是撤销,在这里我们输入u即可
有复制就有剪切,我们输入dd即可
这里就是我使用dd将printf剪切到最后一行,另外,我们dd后如果不p的话,就等于删除了
dd也是支持ndd的,比如我们在起始位置100dd,就是删除100行(不粘贴回去的话)
我们上面的光标定位都是上下定位,除了上下定位,我们还可以左右定位
此时我们的光标在左侧,我们想让光标到达该行的最右侧 ,我们可以shift $
如果我们在最右侧,想回到左侧,我们shift ^
上面的两个其实就是输入$和^,我这里加shift是防止误解
这两个操作,我们在vim中有一个概念叫做锚点
如果我们不想直接移动到一行的结尾,我们还可以w和b
我们也可以使用左右进行移动,左右键是一个字符一个字符的移动,w和b是按单词进行移动,比如上面的图就是我从这行的开始位置w了一下,w是向后,b是向前
w和b的前面也是可以加n的,但是没啥用,当我们把单词个数数完了,我们早就无脑w过去了
我们可以使用上下左右键移动,我们还可以使用hjkl移动,对应的h是左,j是下,k是上,l是右
功能是一样的,为什么是hjkl呢?其实是vim出现时比键盘有上下键要早的多
这里教大家一个简单的记忆方法,这四个键中,h在最左边,所以h是左,l在最右边,所以l是右,我们平时打游戏时,像那种2d游戏,k就经常是跳跃键,所以k是上,j一般是攻击键,一般我们是用剑或者刀向下砍,所以j就是下
我们想把printf这个单词全部改为大写,我们可以 shift ~
每按一下,就会转换一个字母
再次按shift ~ 就可以切换回小写,shift ~是大小写转换
有时候我们想要修改一个字母,比如我想把printf的p改为q,我们就可以r,然后输入字符
r前也是可以跟n的,比如我们4r
这里就是我输入4r,然后输入x,就把4个字符全变成了x ,所以r就是替换
如果我们想要修改一整个单词,我们可以shirt r,也就是R
我们可以一直进行替换,直到我们输入esc为止(我们shift r之后左下角是会变的)
我们还可以删除字符,x即可,按一下x删一个,x也是可以和n结合,即nx
我们还可以对撤销进行后悔,用上面的图举例,我们删除了cout这一行,然后我们后悔了,我们输入u,cout这一行就恢复了,但是此时我们又后悔了,我们就可以ctrl r,大家可以试一试删除一行,然后u,再ctrl r,然后一直重复u,ctrl+r进行观察
以上都是单文件的操作,那多文件呢?
我们这里是没有code.c的,我们vim一下
然后退出
还是没有code.c的,如果我们vim之后wq,就会多一个code.c的文件
所以vim是可以创建一个文件的
我们在code.c里随便写一点代码,此时如果我们想形成一个新的文件,比如code1.c
我们先进入底行模式,即shift :
我们vs code1.c,vs是打开新文本
此时左边的就是code1.c,右边的是code.c
我们未来可以打开多个文本进行写代码,但是代码在哪里写取决于光标在哪里,我们ctrl ww可以切换文件,比如此时我们光标在code1.c,我们ctrl ww
光标就到了code.c里
我们可以在code.c里10 yy,然后ctrl ww切换到code1.c里,然后p一下,就可以把右边的代码复制到左边
此时我再把code.c里的printf多复制几行,然后两边都wq,光标在哪里,wq的就是哪一个文件,然后我们可以多一个code1.c的文件
以上就是我们经常使用的一些,下面我们进行总结,总结里会比上面讲的要多一些,大家看看就行了,因为很多都不常用
移动光标vim 可以直接用键盘上的光标来上下左右移动,但正规的 vim 是用小写英文字母「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移一格按「 G 」:移动到文章的最后按「 $ 」:移动到光标所在行的 “ 行尾 ”按「^」:移动到光标所在行的 “ 行首 ”按「 w 」:光标跳到下个字的开头按「e」:光标跳到下个字的字尾按「b」:光标回到上个字的开头按「 #l 」:光标移到该行的第 # 个位置,如: 5l,56l按[ gg ]:进入到文本开始按[ shift + g ]:进入文本末端按「 ctrl 」 + 「b」:屏幕往 “ 后 ” 移动一页按「 ctrl 」 + 「f」:屏幕往 “ 前 ” 移动一页按「 ctrl 」 + 「u」:屏幕往 “ 后 ” 移动半页按「 ctrl 」 + 「d」:屏幕往 “ 前 ” 移动半页删除文字「x」:每按一次,删除光标所在位置的一个字符「 #x 」:例如,「 6x 」表示删除光标所在位置的 “ 后面(包含自己在内) ”6 个字符「X」:大写的 X ,每按一次,删除光标所在位置的 “ 前面 ” 一个字符「 #X 」:例如,「 20X 」表示删除光标所在位置的 “ 前面 ”20 个字符「 dd 」:删除光标所在行「 #dd 」:从光标所在行开始删除 # 行复制「 yw 」:将光标所在之处到字尾的字符复制到缓冲区中。「 #yw 」:复制 # 个字到缓冲区「 yy 」:复制光标所在行到缓冲区。「 #yy 」:例如,「 6yy 」表示拷贝从光标所在的该行 “ 往下数 ”6 行文字。「p」:将缓冲区内的字符贴到光标所在位置。注意:所有与 “y” 有关的复制命令都必须与 “p” 配合才能完 成复制与粘贴功能。替换「r」:替换光标所在处的字符。「 R 」:替换光标所到之处的字符,直到按下「 ESC 」键为止。撤销上一次操作「u」:如果您误执行一个命令,可以马上按下「u」,回到上一个操作。按多次 “u” 可以执行多次回复。「 ctrl + r 」 : 撤销的恢复更改「 cw 」:更改光标所在处的字到字尾处「 c#w 」:例如,「 c3w 」表示更改 3 个字跳至指定的行「 ctrl 」 + 「g」列出光标所在行的行号。「 #G 」:例如,「 15G 」,表示移动光标至文章的第 15 行行首
上面的这些都是命令模式的,我们再来看看底行模式的,底行模式也叫做末行模式
底行模式命令集
首先我们输入冒号进入底行模式
我们可以w!强制保存,q!强制退出,wq!强制保存退出,防止出现意外
在底行模式中,我们可以使用!来直接执行命令
比如我们执行ls命令,在这里我们就可以直接gcc编译文件,然后./a.out运行
我们还可以调出行号
set nu即可,可以调出,就可以去掉
set nonu就是去掉行号
列出行号「 set nu 」 : 输入「 set nu 」后,会在文件中的每一行前面列出行号。跳到文件中的某一行「#」 : 「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字 15 ,再回车,就会跳到文章的第 15 行。查找字符「/关键字」 : 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止。「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直 按「n」会往前寻找到您要的关键字为止。保存文件「 w 」 : 在冒号输入字母「 w 」就可以将文件保存起来离开 vim「q」:按「q」就是退出,如果无法离开 vim ,可以在「q」后跟一个「!」强制离开 vim 。「 wq 」:一般建议离开时,搭配「 w 」一起使用,这样在退出的时候还可以保存文件。
vim的配置
vim是可以进行配置的,我们可以把vim配置的和vs一样,写代码时有提示,有各种颜色等等
首先我们要来到家目录下,我们用touch创建一个.vimrc的文件
然后我们vim进入到这个文件里
我们先写一个set nu,然后保存退出
此后我们再vim时,文件都会自动带有行号
那我们该如何配置呢?这个大家百度一下vim配置项即可
vim的配置原理就是这样的
另外,我们在bai里面的vim配置是不会影响zhangsan和admin的
还有我们是不推荐给root做配置的
这里是给root做配置的地方
知道大家都很懒,所以这里提供一个简便的办法
首先我们要确认不在root的账号下,然后我们直接在命令行输入
curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh
这是一个大佬提供的,然后我们按照步骤,输入root的密码就可以了
最后按照步骤输入即可
这个非常强大,什么括号补齐,代码提示,颜色区别都有
不过目前只支持在centos7,其他的是不支持的
这个文件我们推荐隐藏起来,未来不想用删掉就行了
这个配置缩进还有一点问题,我们稍微修改一下
我们vim 这个.vimrc
我们搜索一下2
把这三个2改为4即可,这三个是tab之类的宽度,修改一下更符合我们平时写代码的习惯
然后重新生效一下,即输入上面的source那个就可以了
解决sudo的白名单问题
我们之前是无法sodo的,因为我们的账号没有被添加到白名单,下面我们来解决这个问题
添加到白名单需要root账号,我们先切换过来
我们要把用户添加到sudoers这个文件里,这个文件的权限对拥有者和所属组都只有读权限
我们要以root的身份打开这个文件
然后我们往下翻,翻到100行左右有这样一个
我们yy,p复制一下,然后修改root为我们的用户名
然后我们保存退出即可,不过我们在退出时会出现这样的报错 ,所以我们要wq!
接下来我们就可以使用sudo了,使用sudo需要我们输入自己的密码,我之前已经输过一次了,所以这里没有再输入
我们可以看到这个文件的拥有者是root
root可以将普通用户添加到白名单里,也可以删除掉白名单里的用户,所以大家不要以为有了sudo就可以为所欲为不受限制了,即使我们用sudo把root干掉,也不会影响root,要记住,Linux下root是真正的管理员
Linux编辑器—gcc/g++使用
我们在编译代码时会使用到gcc和g++,gcc只能编译c语言,g++既可以编译c语言,也可以编译c++,他们俩对应的选项几乎一样,所以我们只讲解gcc,g++平替即可
我们创建一个新的.c文件,然后在里面随便写点代码
这里再教大家一个小技巧,按住alt键,然后按回车,就可以切换xshell的全屏
然后我们直接gcc .c文件即可 ,这里就是编译,然后就会出现一个a.out的文件,a.out是默认形成名字
我们要运行代码的话直接 ./a.out 即可
就像上面这样,另外,如果我们的代码有错误,我们在编译时就会报错
我们知道一个文件从文本文件经过编译等等操作变成可执行程序要很多步骤,大致可以分为四步
1. 预处理(进行宏替换 )2. 编译(生成汇编 )3. 汇编(生成机器可识别代码)4. 连接(生成可执行文件或库文件 )
这些我在往期博客里进行过详细讲解,有不了解的可以去看看
(15条消息) 程序环境和预处理_KLZUQ的博客-CSDN博客
在预处理阶段,会有去注释,头文件展开,宏替换等等
我们重写一下代码,加点宏,注释等等
我们使用-E选项生成test.i文件,然后我们打开和test.c对比一下
对比发现,test.i里的代码变到了850行,这是头文件展开,而且发现,注释没了,宏(也就是我们写的M)被直接替换 ,这些内容我在上面的博客里都详细讲过,这里就不再多说
现在我们再看一个问题,为什么我们可以在Windows或者Linux上进行C/C++或者其他形式的开发呢?
我们在第一次写C语言时,写的是#include <stdio.h>,我们现在知道他是头文件,既然叫做文件,说明我们的系统中一定要提前或者后续安装上C/C++开发相关的头文件和库文件
所谓的C/C++开发环境不仅仅是vs,gcc,g++,更重要语言本身的头文件和库文件等等
那么在Linux下这些头文件在哪里呢?
在这个文件里,就有我们的stdio.h等等
我们打开stdio.h,里面就有八九百行,我们的代码为什么刚才会变成800行,就是因为他把这个文件拷贝过去了
所以我们要写C语言,不是只安装了vs,我们当时还选择了各种开发包,这个开发包不仅仅是下载vs,还下载了对应的头文件和库文件
所以,我们也是可以在Windows上找到我们的头文件和库文件的
我们再给文件加上条件编译
接着我们再次编译
由于我们没有定义DEBUG,所以这里保留了release
我们还可以不进入文件,直接在命令行使用gcc修改代码,添加宏
编译器可以对代码进行去注释,宏替换,头文件展开等等操作,自然在编译时也可以帮我们添加一个宏
条件编译有什么用呢?
我们在下载软件时,会听说过社区版和专业版,比如开发java使用的IDEA,开发python使用的pycharm等等,不过专业版是付费的
这些专业版里有更多,更强大的功能,而软件公司需要维护两套代码码?一份专业版,一份社区版,答案是不需要的,只需要加上条件编译,根据不同的编译条件裁减掉社区版不需要的功能即可
这就是条件编译的一个应用场景
我们回过头来继续看
使用-S选项可以生成汇编代码
当我们有了汇编后,我们就可以通过汇编形成机器可以识别的二进制代码
使用-c选项即可,我们打开看看
里面是各种乱码,我们是看不懂的,因为test.o是二进制,而vim是文本编辑器
所以我们可以使用二进制查看工具,比如od来进行查看
当然二进制我们也是看不懂的 ,我们了解一下即可
最后经过连接,就可以生成可执行文件或库文件
我们重命名为mytest,然后运行一下
我们在上面使用了各种选项,比如E,S等等,下面我们来看看这些选项
gcc 选项-E 只激活预处理 , 这个不生成文件 , 你需要把它重定向到一个输出文件里面-S 编译到汇编语言不进行汇编和链接-c 编译到目标代码-o 文件输出到 文件-static 此选项对生成的文件采用静态链接-g 生成调试信息。 GNU 调试器可利用该信息。-shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库 .-O0-O1-O2-O3 编译器的优化选项的 4 个级别, -O0 表示没有优化 ,-O1 为缺省值, -O3 优化级别最高-w 不生成任何警告信息。-Wall 生成所有警告信息。
我们使用gcc,如果什么也不加,就默认生成可执行程序a.out
如果我们想一次生成可执行,并且想给可执行程序重命名,就使用-o选项
这里是可以随意命名的,命名为test.txt代码也是可以跑的
未来我们也是推荐使用-o来命名的
也可以这样写,总之-o后跟的是我们的重命名 ,原文件放在哪里都可以
如果我们要只进行预处理
我们使用-E选项,-E后跟原文件,后面还要跟-o,如果不跟-o,默认会把文件打印在屏幕上,后面的-o重命名名字一样可以随便起,不过我们一般以 .i 结尾
-E就是告诉gcc从现在开始进行程序的翻译,将预处理工作做完就停止,不要继续往后走
然后我们要进行编译,将c语言变为汇编语言
我们使用-S选项,同样的S选项后跟原文件,我们可以从.c文件开始,也可以从.i文件开始,然后会生成汇编文件,我们后面继续跟-o重命名,汇编文件的后缀我们一般以 .s 结尾
-S就是从现在开始进行程序的翻译,将编译工作做完就停止,不要继续往后走
然后就是第三阶段汇编,也就是将汇编语言翻译为二进制语言
-c是从现在开始进行程序的翻译, 将汇编工作做完就停止,不要往后走
我们推荐把这个二进制文件以.o结尾,这个二进制文件叫做可重定位目标二进制文件,简称目标文件,这个.o文件, 在Windows下就是 .obj 文件, 这个文件不可以独立执行, 需要链接才行
我们看上面的.o 文件,是没有x权限的, 即使我们使用chmod加上x权限也是不行的
有可执行权限和有可执行能力是两件事, 就像一个人可以去参加运动会, 但他能不能拿奖是另一回事
最后一步就是链接的过程,作用是将可重定位目标二进制文件和库进行连接,形成可执行程序
这么多我们该怎么记忆? 整个过程分为四步,但最后一步连接是没有选项的
前三步我们看键盘上有esc键, 对应的就是-E -S -c,这样就可以轻松记忆
我们形成的临时文件,这些.i .s .o ,我们可以记为iso , iso是镜像文件的后缀, 这些都是帮助我们记忆的一些简单技巧
我们代码里经常用到printf,我们知道这是一个函数,我们这是函数调用,但我们知道,函数应该有声明和定义,那定义在哪里呢?就在库里面
在c语言阶段,经常可以听到标准库,那标准库在哪里呢?
我们在这里就可以找到库,所以库的本质就是一个文件,根据平台不同,格式也不同,比如Linux下以.so结尾的,我们叫做动态库,.a 结尾的叫做静态库,Windows下以 .dll结尾的叫做动态库,.lib结尾的叫做静态库
而这些库一般是有自己的命名规则的,比如前面以lib,然后是name,最后是以.so结尾,后边还可能有个xxx
这也是上面的这个库为什么是c的库,我们去掉前缀,去掉后缀,就只剩c了
目前我们的机器上默认只安装了动态库,静态库是没有的
在编译型语言,比如C语言,安装下载必定是头文件和对应的库文件
库其实就是把源文件(.c文件)经过一定的翻译,然后打包,只给我们提供一个库文件即可,不用提供太多的源文件,这样还可以达到隐藏源文件的目的
所以,头文件提供方法的声明,库文件提供方法的实现,再加上我们自己写的代码,就等于我们的可执行程序(软件)
库存在的意义就是不让我们做重复的工作,站在巨人的肩膀上
下面我们来讲解.o文件和库是怎么链接的
有两种链接,一种是动态链接,另一种是静态链接
我们先来讲动态链接,假设小明今年考上了高中,小明平时喜好玩游戏,但高中是不允许带手机等电子产品的,所以小明在假期时询问了学长,得知学校附近有一家网吧,然后有一天周末,小明为自己制定了一天的计划,比如:1.写语文作业,2.写数学作业,3.写英语作业,4.去玩游戏,5.写物理作业....于是说干就干,小明按照顺序执行,当到达去玩游戏时,小明想起了学长曾经告诉他学校附近有一家网吧,于是小明去网吧玩了会游戏,接着继续回到学校完成计划,写物理作业...
我们看这个过程,小明按照顺序执行计划,就像我们的代码,按顺序执行,我们可以把网吧当作动态库,当可执行程序到达一个地方时,发现自己无法完成,需要去执行库里面的方法,比如小明想要玩游戏,但无法自己玩,就需要去网吧,当把库中的方法执行完,就会回到程序中继续执行,小明知道网吧在哪里是因为小明提前询问了学长,学长就相当于编译器,在小明的大脑里注册了对应的方法,上面小明去网吧的这种方法就叫做动态链接
而一个学校里会有很多人,比如小红,小李,小张,他们都可能问过学长,知道网吧在哪里,所以网吧是所有人都可能会去的,是共享的,所以动态库也被称为共享库,而且所有人去网吧上网只需要去这个网吧就行,所以只需要一个网吧
有一天,这个网吧被派出所查封了,而再有同学执行自己的计划时,发现不能执行玩游戏了,所以,动态库不能缺失,一旦动态库缺失,影响的就是多个程序
我们可以使用ldd指令来查看我们可执行程序所依赖的动态库 ,后面的0x00什么什么就是地址
当我们把这个库删掉后,我们的程序就无法运行了
同样的,ldd还可以查看我们的指令所依赖的库,所以,我们的很多指令也都是C程序 ,比如ls也是C实现的
换句话说,我们把C的库删了,我们在Linux下大部分指令都无法运行
当网吧被查封后,我们的同学还是有上网需求的,我们有两条路可以走
一条是回去告诉家长,我现在需要上网,家里人说好的,会帮你解决
另一种是网吧被查封后,网吧的老板换了一个身份,他开始卖电脑了
然后我们的家长就从老板这里买了一个电脑,于是我们就成为了一个有电脑的人
所以未来我们执行计划时,发现不用去网吧上网了,因为我们有电脑了,而别的同学没有电脑是不能执行计划的,我们这种把拿到电脑可以不用去网吧就可以继续执行计划的方式就叫做静态链接
而这个老板就相当于静态库,当可执行程序需要时,他并不像我们的之前的学长,告诉我们网吧的地址,而是直接把电脑给我们了,直接把库里面的方法拷贝过去
而又有一天,这个老板的店又因为各种原因被查封了,而我们很多人是不知道的,也不关心
在编译器使用静态库进行静态链接时,会将自己的方法拷贝到目标程序中,而未来程序就不会再依赖静态库了,这就叫做静态链接
不管是动态库还是静态库,都只是两个文件
在Linux中,编译形成可执行程序,默认使用的是动态链接
我们不能跑到网吧里直接问,老板,你这电脑多少钱一斤,也不能去电脑店里去上网玩游戏,所以,动态库只提供动态链接,静态库只提供静态链接
我们想使用gcc静态链接的话,需要在后面加上-static选项
对比可以发现,静态链接的程序大小会比动态链接的要大
当我们ldd这个mytest-static时,就会显示
告诉我们这不是一个动态链接
我们的Linux下默认只装了动态库,当我们想按照动态库或者静态库时,我们可以百度一下centos yum安装C/C++静态库
当然,我在这里也会说一下,我们安装一个C语言静态库
sudo yum install -y glibc-static
C++的静态库是
sudo yum install -y libstdc++-static
如果我们没有静态库,是不能使用-static选项的
如果没有动态库,只有静态库,而且gcc能找到库在哪里,是可以的,gcc默认优先动态链接,找不到会静态链接,都没有才会报错,-static的本质是改变优先级
我们学校附近不会只有网吧,比如我们会提前问学校附近哪里有篮球场,ktv等各种地方,所以我们的程序也是需要依赖各种各样的库,各种动静态库
所以,其实我们形成的可执行程序不一定是纯动态链接或者静态链接,可能是混合的
但是我们加上-static选项就会把所有链接变为静态链接,变为纯静态链接
我们可以使用file指令查看自己的可执行程序是动态的还是静态链接的
最后我们总结一下动静态链接的优缺点
动态库是共享库,可以有效的节省资源(磁盘空间,内存空间,网络空间)
动态库一旦缺失,会导致各个程序无法运行
静态库不依赖库,程序可以独立运行
静态库体积大,比较消耗资源
下面我们来讲讲debug和release
gcc形成的可执行程序默认形成的是release版本,如果是debug版本可以被追踪可以被调试,原因是形成可执行程序时添加了debug信息,所有内存会比release大
我们可以在使用gcc时添加-g选项,这样生成的就是debug版本
我们对比就可以发现,debug版本是比release版本大的 ,不过不像动静态的差距那么大
静态链接也是可以使用-g选项的
这就是添加了调试信息,我们来证明一下
我们可以使用readelf -S来读取可执行程序对应的二进制程序,会将对应的空间布局情况,对应的属于只读数据区,全局数据区等等显示出来
如果我们在后续跟上grep就可以显示出debug信息,所以-g选项就可以发布debug选项
我们的可执行程序形成的时候,不是无序的二进制构成的,而是有自己的格式的,在Linux中叫做ELF格式,这个大家感兴趣的话可以去了解一下
我们上面讲的这些东西,对于g++也是同样使用的,g++的选项和gcc是一样的
Linux项目自动化构建工具-make/Makefifile
我们上面使用gcc完成的源文件只有一个,当我们未来要完成一个项目时可能有几百个文件,我们到时候编译的话不可能像现在一样手动去输入gcc什么什么,哪怕你真的去输了,那删除的时候呢?如果一不小心把源文件删除了怎么办?所以就需要自动化构建工具
make是一条指令,makefile是当前目前下的一个文件,我们来见识一下
我们首先要在目录下创建一个makefile的文件(首字母可以不大写),然后我们vim进入里面
我们看这两行,第一行叫做依赖关系,第二行叫做依赖方法,mytest.exe是我们的可执行程序,mycode.c是源文件,细节是第二行以tab键开头
然后我们未来就不需要手动去gcc了,只需make一下即可 ,他会在我当前目录下找到makefile文件,按照我们的预定来处理文件
此时我们就可以 运行文件了
有了编译,就需要有清理
我们看第四行和第五行这两行,clean是清理,他的依赖关系为空,第五行是依赖方法,只不过我们未来习惯使用.PHONY这样的关键字来修饰clean,即第三行
此时我们清理项目就可以make clean
此时我们就可以随意修改我们的代码,也就是修改我们的mycode.c,然后使用make 和make clean即可,不用手动去gcc,可以提升我们的效率
下面我们来详说一下依赖关系和依赖方法
举个例子,小明是一名学生,家里每个月都会给他打生活费,这个月小明把钱花完了,用学校的电话,给家里打电话,说“爸爸,我没钱了,给我打钱”,这句话里,爸爸就是依赖关系,如果只有依赖关系,家里人是不知道小明要干什么的,而我没钱了,给我打钱是依赖方法,只有依赖方法也不行,如果不是小明,家里人是不会给打钱的,有了关系才能进一步,两者缺一不可
现在我们要把源文件mycode.c编译形成mytest.exe,我们最终的目标是要有mytest.exe
我们要先告诉make这个工具,说mytest.exe依赖于mycode.c这一个源文件,有两个就说两个,有三个就说三个,先表面依赖关系,目标文件是mytest.exe,光有依赖关系不行,还要有依赖方法,也就是下面一句,所以gcc就是根据依赖关系,把mycode.c,形成mytest.exe
下面我们把makefile写的麻烦一点,可以看到整个编译过程
我们上面详细进行过的步骤里,最后可执行文件是依赖于.o文件的
但我们此时目录是没有.o文件的
.o文件要依赖.s,我们前面说过iso,所以我们要全部写上,最终来到.c位置 ,我们写了这么多,只有.c文件是存在的
所以我们加上依赖方法,就是我们的指令
我们补全指令,为了方便,我这里把mytest.exe修改为mycode
然后我们make一下,可以发现,生成了我们要的所有文件
我们再想想,我们在makefile文件里写的时候是从上往下写的,而代码运行是从下往上的
其实make在扫码makefile时,扫码第一行没有.o文件就要先形成.o,.o又依赖.s,但是也没有.s,所以先形成.s,以此类推,我们发现make会根据依赖关系表来递归形成依赖的文件,最后的mycode.c是递归出口,是一个栈的结构,我们称为makefile的自动化推导
所以顺序是不影响的,我们乱序也是可以跑的,就像我们c语言函数可以找到就行了
实际中我们不要写这么多,能一行写完绝不两行,我这里只是演示
如果这里面的我们故意少些一行,就会报错
我们再补全clean,多文件之间用空格隔开
我们把clean写在前面
然后我们再make就变成了rm -f了
此时我们就需要make mycode才能编译文件
我们之前也是可以通过make mycode来编译文件
make是自顶向下扫码makefile,把我们要形成的第一个目标文件充当make的默认动作,所以我们建议一般不要把clean放在最前面
make后面可以直接跟目标文件,指定名称执行该依赖关系和依赖方法
我们再看这个问题,为什么我们只能make一次,后面就不让make了,当我们修改一下代码,就可以再次make了,这是什么原因?
我们第一次编译后,make就没必要帮我们再编译了,因为再形成的文件还是一样的,这样做可以提高编译效率,我们现在的代码还是太少了,以后几十万几百万行代码一编译就是半小时,是很恐怖的
那他是怎么做到的?答案是根据文件的修改时间
一般情况下是先有源文件,再有可执行程序,源文件的修改时间是在可执行程序形成时间之前的,当我们修改源文件后,源文件的时间就在可执行之后了,make就会比较这两者的时间,然后根据这个判断是否需要进行编译
那原文件和可执行的时间是否可能是一样的?一般是不会的
下面我们来证明一下我们上面说的
我们可以用stat指令来看时间,Access是最近访问时间,什么是访问?我们不修改他,哪怕我们cat看一下这个文件都是访问,这个时间的修改是非常频繁的,Modify是修改,Change是改变,这两个是不一样的,我们之前说过,文件=文件内容+文件属性,只修改文件内容是Modify,而对文件属性做修改是Change
这三个时间不是割裂的,是相关的, 比如我们修改文件内容,这三个时间都是会变的,因为文件的大小变化会影响文件属性,所以这三个都会变,他们三的变化要根据场景来说
比如我在这里进入文件里随便删点东西,然后我们看这三个时间就变成一样的了
我们这里给所属组去掉x权限,然后发现只有change改变了,这里大家可能疑惑了,为什么access不变呢?他不是修改最频繁的时间码?
正是因为access修改太频繁了,早期Linux的各种操作都会使accsee变化,可是后来Linux设计者发现,对于任何一个文件,access修改频率太高,是需要去磁盘修改的,而磁盘是计算机的外部设备,当我们有多个用户时,access的修改频率是非常恐怖的,这些更新行为都要被写入磁盘,最后会变得非常非常慢,不利于Linux整机效率提高,所以后来就修改了这个规则,我们访问三四次,四五次,他才会修改一次,变相提高Linux整机效率
如果我们就想要修改所有的时间
我们可以touch一下,如果touch后跟的是已存在的文件,他会把文件的所有时间更新为最新
也可以加-m选项 ,修改modify时间,不过modify时间修改后,一般change时间也会跟着变
可以加-a选项,修改access时间
所以我们想多次make时,可以用touch来完成
我们上边说了,因为会比较时间的原因,所以我们的make不一定会执行,如果我们想要让他总是被执行呢?
我们可以用.PHONY来修饰文件,phony的意思是总是执行
之后我们就可以多次make了 ,不过我们不建议在可执行文件这里加.PHONY,但是清理工作我们是希望的,这也就是我们上面说为什么习惯用这个修饰clean
下面我们再来看特殊符号
我们在gcc这里是不用写那么长一串的,只需要gcc -o $@ $^ 即可 ,$@代表上面依赖关系冒号左侧的,$^是冒号右侧的
我们每次make,clean时都会把我们的依赖方法显示出来,如果我们不想让他显示的话,可以
在依赖方法前加上@
这样就不会显示了
小程序——进度条
我们希望最后制作出的进度条是这样的,一个方括号,里面是#或者-,有一个>作为箭头,右边还有当前进度,百分之多少,最右边还有一个斜杠一直旋转
预备知识:
1.回车换行,我们C语言里 \n就是换行,不过其实回车是回车,换行是换行
假设我们在一个笔记本上写字,笔尖就相当于我们的光标,当我们写了一半时想要换行,于是在另一行开始写,我们要把笔尖挪到下一行时,我们假设我们竖直挪动,这个叫做换行,而当我们再挪动到下一行的最开始位置时,这个叫做回车,回车换行是两个动作
如果我们只想回车该输入什么?输入 \r 即可
我们看这个打字机,我们在打字的时候,这张纸会不断的往上走,这个就是换行,而左边我标准1的位置,这个在打字的时候有移动到右边,然后我们需要手动把他搬回左边,这个就是回车
如果大家家里有老式的键盘会发现,右边的回车键非常大,像一个L一样,和这个非常像
2.缓冲区
我们先创建两个文件,一个是进度条的声明,一个是实现
还需要一个main.c,我们先进入头文件
我们再创建makefile帮助我们编译删除
我们需要sleep函数的帮助,他在unistd.h里,作用是使程序休眠,比如这里就是休眠2秒
此时我们的程序就在休眠,2秒后才结束
我们把printf后的\n去掉,大家猜猜会先运行哪一行代码?
答案是21,我们来验证一下
但真的是这样码?
C语言在执行时,我们看到上面的现象就是先休眠两秒,然后打印,打印出的字符在下一行的最左边,因为打印没有换行符,所以shell命令行就紧跟着字符,但是,c语言是按顺序执行的,所以一定是先打印,再休眠的,当我们在sleep两秒期间,其实printf早就执行完了,只是消息没有显示出来而已,在sleep期间,这个hello world在哪里呢?一定被保存起来了,被保存就需要一段内存的,其实就是被保存在了缓冲区里面,缓冲区的详细知识,我们后续会讲,我们知道这里的缓冲区就是由c语言维护的就行了
我们怎么证明呢?
c程序会默认打开三个流,标准输入流,标准输出流,标准错误流,而Linux下一切皆文件,这里的输出就是显示器,stdout
用man手册可以查看到,stdout就是file*类型
我想要把数据强制刷新到显示器上该怎么办呢?
fflush,就是刷新一个流
于是我们修改main.c,然后再次执行
这就我们就可以看到,是先打印,然后休眠两秒,然后结束
以上就是我们的预备知识
假设箭头是我们的光标,我们希望写一个9,光标在9的后面,我们要让光标回到最开始的位置
然后我们再写一个8,我们希望光标再次回到前面,然后写7,以此类推 ,这样我们就可以实现一个命令行版的倒计时
这里的效果大家最好自己试一试,我们可以想到,他会打印出987654321,但这不是我们想要的,我们要的是倒计时,我们想要把原来的内容覆盖掉,即9,然后9消失变为8,8消失变为7
我们加上\r,此时就完成了一个简单的倒计时
此时我们的倒计时结束后会被命令行覆盖掉,我们在末尾输出一下换行
我们最后修改成这样
有点奇怪,为什么后面会有一个0?
我们认为我们在显示器上打印的是10,其实是字符1和0,只不过1和0挨在一块,所以看着是十,显示器只能打印字符,所以显示器是字符设备!所以我们输入输出,%d什么什么是格式化控制
上面的原因是因为10是两个字符,而我们每次只覆盖一个
所以我们修改为%2d,此时就变成了正常的倒计时,想要居左对齐写成%-2d即可
知道了这些知识,下面我们来正式开始写进度条
首先我们先在.c和.h文件中简单写一下,进行一下测试
我们只需在main.c里调用一下
简单测试,没有问题
此时我们要实现的进度条就变成了实现processbar这个函数
下面我们来讲一下进度条的原理
黑框是我们的显示器,绿色的竖线代表我们的光标,最开始光标在最左侧,然后我们输出一个字符(这里的红色框)
接着我们让光标再次回到左侧,然后输出两个字符
接着我们再次让光标回到左侧,再输出连续的三个字符,以此类推,每一次多一个字符,就会覆盖之前的字符,会从左向右递增,有一个推进的效果,这就是进度条的原理
我们这里假设让他循环100次(即最后会输出100个字符,对应进度百分百)
这里他会循环101次(因为是<=)
然后我们打开.h文件,设置一个NUM,我们最后会循环101次,还有一个\0,所以NUM设为102
我们设置一个bar,对他初始化
我们的进度条需要一个图形,这里我们设置STYLE为#,最后进度条就会变成[########],里面全是#,大家可以根据喜欢来修改
我们使用%s输出bar,然后在bar[cnt]位置放入#,接着休眠1秒
此时就有这样一个结果,但是进度条是不会换行的,这里的原因是我们printf时还输出了\n
除了把\n换为\r,我们还要记得加fflush,因为没有了\n就没有了立即刷新,因为显示器是行刷新的
此时我们的进度条就初步完成了
sleep刷新的太慢,这里我们使用usleep,他的单位是微秒,这里输入10w微秒可以让进度条在10秒左右完成
接着我们补全细节,首先是结尾的换行,还有进度条两边的中括号,以及进度条后面的显示进度(百分比数字),我们要保证进度条的右边的中括号最开始的位置就在终点,所以要预留位置,我们要输出的#有100个,循环进行101次,但是第一次循环时cnt为0,bar为空,所以第一次只输出中括号,其余位置为空,所以这里是%100s,如果只是%100s,那么进度条的#就是从右到左走,原因是c语言给了预留空间后,默认是右对齐,所以输出时是从右向左,所以这里是%-100s
此时就可以正常运行了
下面我们再来添加一个细节,旋转光标,类似下载时的那个旋转的光圈
思路其实很简单,就是在一个位置先输入|,然后在这个位置覆盖输入/,接着输入-,最后输入\,循环即可
\是需要转义的,所以是\\
此时我们的进度条就完成了
进度条进阶
此处是上面的进价版,这里大家选择性观看即可,不涉及新知识,就是带大家玩一玩
v1版
我们先给进度条先加上一个>,这样更有感觉,>的位置直接就是cnt,这里还加了if语句判断临界,如果是<=1000的话,最后是会往前顶一下的,我们讨论一下临界,当cnt为98时,此时是第99次循环,括号里此时有98个=(位置从0开始,0到97有98个),第98的位置是>,%100s是有99个位置(从0到99一共100个),输出结果如上面说的,接着cnt位置(98的位置)变为=,然后cnt++变为99,小于100,第99的位置(即括号中的最后一个位置)变为>,进入下一次循环,刷新显示器,此时输出99个=和一个>,接着第99的位置变为=,cnt++变为100,不进入if,此时括号里全为=,进入下一次循环,刷新显示器,输出100个=,到这里其实已经结束了,有人可能会因为NUM是102而疑惑,其实不用管,为什么我们设置的NUM是102?这可能是因为防止越界问题,因为有100个字符,一个\0作为结尾,一共101个字符,所以设为102
然后我们对代码简单封装一下,我们可以看到,我们加了speed,以及各种define,这样定义方便我们之后的修改,我们在main里边就可以自己定义speed
那正常情况下进度条到底是怎么用的呢?下面我们来看v2版本
v2版
我们的v2版本相当简洁,我们可以看到参数只有一个rate,这个rate就直接是当前的百分比,我们还把bar数组设为全局,还删除了循环,我们只对数据进行打印,其余问题是别的地方该考虑的
.c里修改了参数,别忘了在.h也修改
我们再看main里边的,其中total是我们一共需要下载的,curr是当前已经下载的,初始时为0,然后我们模拟一下下载,只要已经下载的curr小于一共需要下载的total,就进入循环,接着调用processbar,我们传入的参数是一个百分比数,用curr*100/total即可得到,然后进行了某种下载任务,下载部分之后,curr的进度就增加一部分,然后用休眠模拟一下需要的时间,直到下载完成
此时再次make一下,也是一样的结果
进度条的原理是v1版,但调用实际上是v2版
v3版
下面我们再来看看v3版,也是最终版本
其他地方都没有变化,只在main里,我们的下载是在download里进行,代码就多了17行这一行,这里使用回调,我们可以看到最上边定义了callback_t,是一个函数指针类型,我们使用他来调用processbar函数,通过回调来更新进度,对进度条进行刷新显示
此时我们模拟多次下载(这里有一点错误,一会修改)
这里的错误是第一次下载完后,后面的进度条都是满的,原因是.c文件里bar数组是全局的
我们只需要加一个initbar函数用来重置数组,然后在.h文件里也暴漏出来
接着在main函数里加上initbar
当然还有一种办法是
.c的结尾会判断进度,else的话就是大于等于100了,这里就可以initbar
此时问题就解决了
总结一下,v1版本是进度条的原理,我们是需要掌握的,v2版本是不再需要循环,而是考虑实际,按照我们的需求进行打印即可,v3版本是我们加上了场景,下载时下载了多少进度条并不关心,只关心告诉他进度是多少,当我们下载时,有进度就通过回调刷新一下
另外c语言是可以进行颜色输出的,意味着我们可以控制进度条的颜色,让他更好看一点,这里大家感兴趣的自己去搜一下,加一些特定词就行,非常简单
git
三板斧
git我们之前在C语言里就开始使用了,所以这里创建仓库等等就不进行讲解
下面我们来看使用,首先我们先看看git按照没,我们可以git -h,会弹出版本信息,git --help,如果弹出这些东西,说明安装完成
如果没有,那么需要用yum进行安装,输入yum install git 即可
首先我们要把远端仓库进行克隆,后面跟的是仓库的https,这里需要输入用户账户和密码
我打码的地方需要输入手机号,也就是我们创建gitee的手机号,然后输入密码
我的仓库名就是Linux_4_26
我们ls -al可以看到.git,这就是我们的本地仓库,也就是云服务器的仓库
里面有各种描述以及日志等等,我们不要手动对他进行任何修改
接着我们把之前的代码全拷贝过来(我的代码全在这个dir1里)
我们tree一下就可以看到
下面我们需要把这些东西添加到仓库里进行管理
首先我们要git add . 将当前仓库所有不在仓库里的添加进去
接着我们git commit -m “”,这里和C语言那里是差不多的,不过这里必须要带-m和双引号,双引号就是我们的日志,写我们这次做了什么,最好不要乱写
如果大家是第一次提交可能会出现下面这种情况
解决方法也很简单
此时我们就完成了提交
下面我们要把代码推送到远端
首先来看遇到的问题
解决方法是选择其中一个即可
# 执行此命令以选择旧版 git 的默认推送模式,并消除提示信息
git config --global push.default matching
# 执行此命令以选择新版 git 的默认推送模式,并消除提示信息
git config --global push.default simple
接着我们再次push,此时需要输入gitee的手机号和密码,和之前一样,然后就完成了
打开gitee进行确认,没有问题
之后我们只修改源文件也是需要git add的,因为每次添加的不是源文件本身,而是修改的增量部分,比如我们的代码有10行,我们写到了15行,此时我们add的是增加的5行,git是记录变化的内容
git的其他问题
gitignore
首先我们要知道,我们每次提交的记录别人是可以查看的
使用git log,包括用户名,邮箱等等基本信息
开源之后别人就可以把你的仓库克隆下来
比如我们有一个dir2,此时我们是另一个人,比如你的合作者,面试官等等
所以提交记录这个东西不要乱写,因为别人是都可以看到的
我们可以用git status查看状态,最开始我们的目录是干净的,然后我们touch了一个test.c,然后再次status就可以看到提示说有一个test.c文件没有被提交,并且有提示说你想要提交的话就可以git add 文件名
我们add后再次status,可以看到依然是没有提交的,是需要commit的
我们commit后就提交到了本地仓库,然后再次status,说你当前的工作区是干净的
意思是我们把刚才的test.c托管到了本地仓库,此时已经没有需要提交的了
只不过本地仓库并没有和远端仓库进行同步,并且有提示说需要的话可以调用git push可以发送到远端
push之后我们来到gitee就可以看到这里多了一个test.c
此时我们再次status就是什么也没有了,说明本地和远端是同步的
我们可以看到隐藏文件还有一个gitignore
这个文件的作用相信大家也了解,以这个文件里的内容为后缀的,都不会被我们提交
我们可以自己修改,比如我们添加了.pch,.p和.pp,未来这些文件也都不会被提交
这里我们来做个测试,我添加了.p和.pp
然后我们创建三个文件
此时我们status三个文件都会有提示
接着我们进行三板斧
然后我们进入gitee
我们可以看到确实有.p和.pp
但是这三个也都被提交了,这和我们想的不一样啊?
为什么现在提交上来了?其实是我们遗漏了*
我们添加*后再来试一试
此时就只有.ppp被上传了
之后大家想让某些文件被忽略,就这样添加即可
开源和删除
在仓库中按照如图所示的顺序点击即可删除仓库
删除仓库的上面还有清空仓库
点击基本信息
最下面就可以设置开源
Linux调试器-gdb
我们先创建两个文件
然后我们先随便写一点东西,我这里写了几行hello linux
我们在学习C语言时,我们知道有debug和release两种模式
我们在开发时都是使用debug,发布时是release
对于我们来说,debug是可以调试的,release是不能的
为什么debug可以呢?因为debug在形成可执行程序时,会有调试信息
而release不是不能有,是不想有,因为用户是不需要进行调试的,所以全删除了
我们这里输入gdb mycode,最后说没有调试信息被发现,说明当前是没办法的(如果是第一次使用是需要按照gdb的,直接yum install gdb即可)
我们可以使用readelf来读取程序的二进制构成,我们是发现里面有各种代码段,初始化区,只读数据区等等,就是没有debug段
我们再使用管道,发现就是没有debug
gcc默认编译是release方式发布的,无法直接调试
如果要以debug方式发布
必须要携带-g选项,另外这里我们把程序名字改了(只是为了方便区分)
另外如果我们拿来和之前的mycode进行对比,用ll进行查看的话,会发现debug程序是明显要比之前的程序大,多出来的就是调试信息
此时我们就可以gdb 文件,来进行调试
比如输入l,查看代码,q,退出
下面我们来对gdb进行讲解
首先我们有这样一份代码
运行也没有问题
然后我们对可执行程序进行调试
我们可以l 0,l 1,l 10等等来进行查看
gdb是会自动记录最近输入的指令的,我这里只输入了l 0,然后一直回车,他也可以把代码全部显示出来,然后再按回车的话会告诉我们一共有23行代码
我们还可以l 函数名,来对函数进行查看
r表示执行当前程序(这里没有打断点)
我们可以使用b 行号,来对代码进行打断点,还可以b 函数名
使用info b可以查看我们打的断点有哪些,第一列是断点的编号
如果我们想要删除断点,是不能d 行号的,而是使用d 编号
另外如果我们现在打了断点,然后退出测试,然后再次进入调试的话,我们刚才打的断点是会清空的
刚才我们删除两个断点后,我们再次打断点,发现编号是和之前的顺序连着的(这里我没有中途退出)
我们输入r,会在第一个断点停下来,这里是18行,正常情况,下面我们就可能会进行逐语句,逐过程进行调试了
n是逐过程,也就是不进入函数,另外,如果我们的代码有空行,他也会直接跳过,不会在这里显示,调试只显示有效行代码
我们调试完一遍后,刚才打的断点也是存在的,这和我们的vs是一样的
而且下面还多了一行,这一行就是断点的命中信息
使用s是逐语句操作,可以进入函数里
这里第7行之后是第9行,是因为第8行是括号,不属于有效行,所以跳过了
我们在vs还经常使用监视
这里我们可以使用p 变量名来对变量进行查看,如果要查看地址,那就p &变量名
这里大家会感觉好麻烦,vs里我们可以一边调试,一边监视,难道这里不行吗?
也是可以的,我们使用display 变量名,来常显示变量,之后我们每次n,或者s,下面都会显示我们的常显示变量
undisplay是取消常显示,这里和断点是一样的,不能undisplay 变量名来取消,而是需要编号,我们可以看到常显示时,最左边是有数字的,这个就是编号
我们在调试时可能遇到这样一个问题,我发现问题不在这个函数里面,但是我现在陷入了函数的循环,但是我又不想完全退出调试,想看看循环结束后的下一行,该怎么办呢?
我们可以until 行号,这里until 10,发现他并没有到10行,而是到了11行
until可以让我们快速运行代码块,比如跑完循环等等,直接运行到指定行
当我们进入一个函数时,可以使用finish来直接跑完这个函数,还会告诉我们返回到了第几行,以及函数的返回值是多少
那他有什么用呢?未来我们的代码会有非常多的函数,比如有十几二十个函数,如果我们的代码崩溃了,我们需要快速定位,首先我们要知道他在哪一个函数里崩了,这时候就需要finish出场了
我们查找错误都是定位,所以这些知识也全是定位,如果有二十个函数,我们想要5个5个的跑,一般是使用断点,在第一个函数打一个断点,然后第6个打一个,所以这里需要执行到下一个断点
使用c就可以从一个断点直接运行到下一个断点,如果出现报错,说明错误就在这两个断点之间
这里我们有三个断点,如果我们想要禁用第二个断点,但是不删除他,该如何操作?
使用disable 断点编号即可,然后我们info查看,就可以看到在Enb这一行,被禁用的是n,可以用的是y
此时我们调试r的话,直接会到第三个断点,因为1和2已经被禁用
使用enable 断点编号,可以取消禁用
指令汇总
gdb binFile 退出: ctrl + d 或 quit调试命令:list / l 行号:显示 binFile 源代码,接着上次的位置往下列,每次列 10 行。list / l 函数名:列出某个函数的源代码。r 或 run :运行程序。n 或 next :单条执行。s 或 step :进入函数调用break(b) 行号:在某一行设置断点break 函数名:在某个函数开头设置断点info break :查看断点信息。finish :执行到当前函数返回,然后挺下来等待命令print(p) :打印表达式的值,通过表达式可以修改变量的值或者调用函数p 变量:打印变量值。set var :修改变量的值continue( 或 c) :从当前位置开始连续而非单步执行程序run( 或 r) :从开始连续而非单步执行程序delete breakpoints :删除所有断点delete breakpoints n :删除序号为 n 的断点disable breakpoints :禁用断点enable breakpoints :启用断点info( 或 i) breakpoints :参看当前设置了哪些断点display 变量名:跟踪查看一个变量,每次停下来都显示它的值undisplay :取消对先前设置的那些变量的跟踪until X 行号:跳至 X 行breaktrace( 或 bt) :查看各级函数调用及参数info ( i) locals :查看当前栈帧局部变量的值quit :退出 gdb
再看这里,我们可以b 文件 行号,我们可以跨文件来打断点,因为未来我们代码是多文件的,所以这个也需要知道
set var修改变量值,这个用的不是很多,我们在进入循环时,有100次,我们想要直接到第80次循环,就可以用这个
就像这样
我们在代码里加入这样一个调用链,在add里调用他
我们进入函数后,使用bt就可以看到
进入Print后我们再次bt,可以看到Print1也入栈了
再次n,s,可以看到Print2也入栈了,显示的就是整个函数的调用链
我们的代码一般会有很多的变量,如果我们只想查看局部变量呢?比如我们在main函数里就只看main的变量,在add里就只看add的
我们就可以使用info locals,也可以简写为i locals,来查看当前函数的局部变量
以上即为本期全部内容,希望大家可以有所收获
如有错误,还请指正。