即Yellow dog Updater, Modified,它是Linux中一个常见的包管理器之一,它能维护大多数软件,就像App Store和其他应用商店一样。因此,它本身也是一个软件,在Linux下,安装软件通常有两种方式:
软件包:
简单地理解为可执行程序,它是需要被编译的,像Windows系统的镜像(体积很大),经常使用的办法是下载源码然后在本地编译的,例如uupdump。
而yum作为包管理器,在Linux下就起着App Store的作用。简单的安装、升级、卸载等基本操作不在话下。
包管理器是有“源”的,可以认为yum就是从源上下载软件包,由于在国内访问国外源速度很慢,所以建议将源更改为国内源,例如阿里、腾讯、xx大学源。
链接:yum更改为阿里源
yum list | grep xx
xx是软件的关键字,使用这条命令会罗列出所有能下载的、与关键字有关的软件。
grep是一个文本搜索工具。
sudo yum install xx
xx是软件包名。
输入该指令后,如果检索到对于软件包,会提示是否安装(y/n),键入y确认安装。
等待出现complete出现则说明安装完毕。
【注意】
sudo yum remove xx
xx是要卸载的软件名。
Vim是一个强大的编辑器,它可以在Linux、macOS、Windows系统下使用。Vim非常强大,它的参考文档也非常多,是学习Linux必须掌握的工具之一。
Vim采用模式编辑的理念,即在不同的模式下只能该模式下的事,这样做不需要通过复杂的切换,大多数情况只需要依次按下按键,而且越常用的操作,所需要按键的数量越少。在这里仅介绍最常用的三种模式。
i
,进入命令模式。只有在此模式下才能进行文字编写。按下esc
键回到命令行模式;在命令模式下,输入
:+ 功能键
进入需要的模式。
a
、i
、o
;esc
;:
;:
+q
(quit);w
;wq
;q!
;wq!
。现在用vim指令打开一个文件:
vim test1.txt
最下侧显示NORMAL,说明现在处于命令模式,键入i
:
最下侧的标识变成INSERT,说明现在正处于插入模式。
修改文本内容,按下esc
,并键入:wq
按下回车自动退出vim,回到当前工作目录。
按「i」切换进入插入模式「insert mode」,按“i”进入插入模式后是从光标当前位置开始输入文件;
按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;
按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。
「yw」:将光标所在之处到字尾的字符复制到缓冲区中;
「#yw」:复制#个字到缓冲区;
「yy」:复制光标所在行到缓冲区;
「#yy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字;
「p」:将缓冲区内的字符贴到光标所在位置。
注意:
所有与“y”有关的复制命令都必须与“p”配合才能完 成复制与粘贴功能。
【注意】
在使用末行模式前,需确保Vim处于命令模式。键入:
即进入末行模式。
区别:前者向下查找,后者向前查找。
前者搭配n使用,后者搭配N使用。
在使用过功能强大的IDE后,再看原生Vim就显得有点“朴素”,为了提高效率和习惯,常常会搭配插件使用Vim。
在目录 /etc/ 下面,有个名为vimrc的文件,这是系统中公共的vim配置文件,对所有用户都有效。 而在每个用户的主目录下,都可以自己建立私有的配置文件,命名为:“.vimrc”。例如,/root目录下, 通常已经存在一个.vimrc文件,如果不存在,创建它。切换用户成为自己执行 su ,进入自己的主工作目录,执行 cd ~
打开自己目录下的.vimrc文件,执行 vim .vimrc
自己配置有点小麻烦,链接,所以可以使用下面这条命令,自动安装插件。
curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh-o ./install.sh && bash ./install.sh
稍等片刻,你就能得到一个炫酷的Vim,它具有语法高亮,自动补全等功能。
gdb是Linux功能强大的调试器,尽管gdb命令有很多,但我们只需掌握常用的十几条就足以满足绝大多数调试需求。
gdb是一个调试器,它的对象是已被编译的二进制代码。在C语言学习中,我们已经了解了编译链接的原理,在这里结合gcc/g++复习一次。
gcc,即GNU Compiler Collection,它是一个系统社区发布的调试工具,随着使用需求日益增加,它现在更像是一个各种工具的集合体,能根据文件的后缀调用不同的库,例如.c文件调用C语言的库。
现在只需要将gcc看作C语言的编译器,g++看作C++的编译器即可。
下面的讲解都以C语言代码示例:
//编写源文件,不存在则自动创建
vim test1.c
//添加以下代码
#include
int func(int n)
{
int ret = 0;
int i = 1
for(; i <= n; i++)
{
ret += i;
}
return ret;
}
int main()
{
int n = 100, ans = 0;
ans = func(n);
printf("%d\n", ans);
return 0;
}
保存并退出。键入以下指令:
gcc test1.c -o _test1
可以看到当前工作目录下多了一个_test1文件,那么我们可以知道,上面指令中的-o
选项后面跟的内容就是指定生成的可执行程序的文件名,否则Linux下编译后生成的可执行程序名字默认是a.out。
这里其实已经执行完整个编译链接过程了(因为已经生成了可执行程序),如果想要看到只进行预处理后的代码,只需在指令中加入-E
选项。
Linux下各种文件的后缀
同样地,用户可以使用-S
选项,可以看到生成的汇编代码,保存在.s文件中。
注意,不用再加-E选项,否则可能会停留在上一阶段,这取决于选项的顺序。
同样地,用户可以使用-C
选项,查看由汇编代码转换而成的二进制代码。
C语言没有内置输入输出函数,每次要包含的标准输入输出头文件就是一个库,它包含着许多内置的接口。
其后缀一般为.a
,在编译链接时,需要将整个静态库的代码加载到可执行文件中,所以静态库生成的文件一般比较大,但是在运行时就不再需要库文件。
所如果调用一个库中的方法很多,直接将库的接口的==实现==方法拷贝到自己的代码中(已编译)。所以它不依赖库,但是占用资源(代码重复)。
其后缀一般为.so
,与静态库相对,为了节省系统开销,在编译链接时并未将整个库的代码加入到可执行文件中,而是在程序执行是有运行时链接文件加载库。
静态库相当于一个仓库,自己写的代码中需要使用库中的接口,只需将库中对应接口的==地址==填入代码中(已编译)就能找到库中接口对应的实现(实际的函数体),从而达到链接,所以只需要调用库即可。
而gcc、g++默认生成的二进制程序是动态链接的。
首先需要再次强调:gdb(调试)的对象是已经被编译生成的二进制文件。
l(list) + 行号:显示二进制文件的源代码(也就是编译前的代码),接着上次的位置往下列,每次列10行;
l(list) + 函数名:列出某个函数的源代码;
r(run):运行程序;
n(next):单条语句执行;
s(step):进入函数语句;
display + 变量名:跟踪查看一个变量,每次停下来都显示它的值 ;
undisplay:取消对先前设置的那些变量的跟踪;
until + n行:跳至第n行;
bt(breaktrace):查看各级函数调用及参数;
i(info) locals:查看当前栈帧局部变量的值;
b(break) 行号:在某一行设置断点;
b + 函数名:在某个函数开头设置断点;
info break :查看断点信息;
finish:执行到当前函数返回后,停下来等待命令;
p(print):打印表达式的值,通过表达式可以修改变量的值或者调用函数;
p 变量:打印变量值;
set var:修改变量的值;
c(continue):从当前位置开始连续而非单步执行程序;
r(run):从开始连续而非单步执行程序;
delete breakpoints:删除所有断点;
delete breakpoints + n:删除序号为n的断点;
disable breakpoints:禁用断点;
enable breakpoints:启用断点;
i(或info,意即 information) breakpoints:查看当前设置了哪些断点;
q(quit):退出gdb。
注:为了括号内和括号外的选项是等价的。
假设test1.c中已有以下代码:
#include
int func(int n)
{
int ret = 0;
for(int i = 1; i <= n; i++)
{
ret += i;
}
return ret;
}
int main()
{
int n = 100, ans = 0;
ans = func(n);
printf("%d\n", ans);
return 0;
}
且该文件已编译,二进制文件为_test1。
注意:
想要使用gdb进行调试(debug),就必须在gcc编译时增加选项-g(上文未提到),意思是可执行程序包含调试信息,也就相当于在VS编译器下从Release模式切换到Debug模式。
gcc编译器默认是Release模式。
【补充】首先要对gdb进行配置,有的机器默认情况下gdb是缺少配置的,会出现这样的问题(即使加了-g选项):
【原因】缺少配置
【解决办法】
vim /etc/yum.repos.d/CentOS-Debuginfo.repo
打开该文件,将enable置为1,如果该文件不存在,会自动创建,将以下内容粘贴:[debug]
name=CentOS-7 - Debuginfo
baseurl=http://debuginfo.centos.org/7/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Debug-7
enabled=1
sudo yum install -y glibc
;debuginfo-install glibc
。注意
上述指令可能需要root用户或sudo执行。
参考:https://www.796t.com/content/1538198547.html
现在开始对_test1调试:
//编译
gcc test1.c
//用gdb打开
gdb _test1
//用指令r试一下
(gdb) r
由于没有以调试模式编译test1.c,那么得到的二进制文件中就不会有调试信息,gdb也就无法对其调试,这也是Debug版本的程序体积更大的原因。
蓝色部分依然打印出了程序的结果,原因是命令r是运行程序,且程序没有断点(也不会有)。
键入q
退出gdb。
//编译
gcc test1.c -g
//用gdb打开
gdb _test1
//用指令r试一下
(gdb) r
最下面的提示表明调试信息已经加载成功。
r
运行程序,l
显示源码:l
可以指定行号范围打印,也可以l -
、l +
向下或向上打印。
r
是运行完整个程序。
break + 函数名
,给指定函数打断点;b + 行号
,给指定行打断点;i breakpoints、i b
,查看断点信息。r
运行至最近的断点处,无断点则运行整个程序;n
单句执行,s
遇到函数则进入函数;p + 变量
打印变量的值;display + 变量
每次执行语句都打印变量的值;undisplay
取消;c
从当前位置从单步->连续执行,直到断点或执行完毕;delete + 断点序号
删除断点;delete breakpoints
删除所有断点;gdb是一个非常强大的调试器,不仅限于C/C++,熟练使用指令后,效率甚至比IDE还高,动手练才是王道。
首先要说明,make是工具(软件),Makefile是一个统称的文件,它的内容可以由用户按照格式自己编写,它的名字也可以是makefile、GNUMakefile,习惯上约定俗成地使用Makefile。
下面分别介绍Makefile和make。
想象一个场景:实际上项目都是有很多个源文件组合而成的,像上面g使用gcc的方式,一次只能编译一个或者几个源文件,这对不计其数的项目文件来说杯水车薪,难道要一直gcc、g++吗?就算这样,难道一个程序不用调试就直接发布吗(多次编译)?而且有时需要文件编译的顺序不同,是否重新编译,等等。
为了提高效率,人们把gcc、g++这些指令都集合在一个文件Makefile
中,只需编辑一次,有新需求再对其修改,通过工具make
,自动化执行指令。
也就是说,make充当了每次输入gcc、g++编译命令的我们,Makefile对于我们而言,就相当于一个大指令。
目标文件 : 依赖文件
指令
或
目标文件 : 依赖文件;指令
指令
「目标文件 : 依赖文件」统称为依赖关系,「指令」则叫做依赖方法,它们是组成Makefile不可缺少的部分。
【注意】
「依赖方法」前必须用
tab
,不能用空格代替。且依赖方法必须在目标文件下一行。
下面用一个简单的例子演示make使用Makefile:
其中Makefile的内容是:
这就是它们最基本的用法,有没有很简单。下面补充更多基础用法。
实际上,清理项目并不是真正意义上的“清理”,因为make和Makefile本身就是为了构建项目(看它们的英文名字),清理项目实际上就是让make执行了清理的指令。
直接给出清理项目需要的Makefile代码:
举个例子:
在下面将说明这里的蹊跷之处。
对于Makefile:
//我这里的tab占2个空格
_test1 : test1.c
gcc test1.c -o _test1
.PHONY : clean
clean:
rm -f _test1
而言:
只要键入make
指令:
Makefile
或makefile
的文件;【注意】
make只会处理依赖关系,不会处理是否编译。
言外之意:如果依赖关系中,
:
后面的文件不存在,这是无法编译的,但是make不会对其检查。这是稍后解释清理项目的原因。
如果将Makefile中的两个依赖关系语句调换位置:
.PHONY : clean
clean:
rm -f _test1
_test1 : test1.c
gcc test1.c -o _test1
因为默认是从上到下查找文件(注意刚刚已经更改了两个语句块的顺序),所以make执行的是rm指令。上文提到,Makefile是由依赖关系和依赖方法组成,它们通过目标文件绑定在一起,这样我才能用make+目标文件名
的形式使用特定的指令。
这也是上文中项目清理不是纯粹的“清理”的原因,因为我指定了目标文件名clean
,但这里有个问题,为什么clean
是一个动作,这里却把它叫做目标文件呢?
看到这个小标题你大概就已经猜到,这是用某种手段将clean
这个动作让make工具以为它是一个目标文件。
首先要介绍.PHONY
,它之于make工具,就像关键字之于C。被它修饰的目标文件叫做伪目标。将make clean
和make _test1
结合起来看,它们都是指定目标文件make,这就是刚才说make清理项目并不纯粹的原因,因为清理的动作是额外实现的。清理项目是没有诸如.c这样的源文件的,加之以make不会对是否能编译进行检查,所以clean的“骚操作”才显得合理。
伪目标总是根据依赖关系,执行依赖方法。
这句话对理解伪目标和它的作用非常重要,但是许多地方都把这句话说成“总是被执行的”,让人费解。我猜可能是直译过来的,突然好想吐槽= =,算了不说了,懂得都懂。
下面解释这个“总是被执行的”到底是什么意思(这里Makefile已经恢复原样):
我在这里多次make,它提示目标文件已存在是最新的了,但是我多次clean,即使当前目录没有目标文件,它依然执行rm
命令,这就是“总是被执行的”。
Makefile文件写到这种程度对初学者而言已经足够,在后续的学习和工作中会有更多不同的需求。