目录
[一、Linux 软件包管理器 -- yum](#一、Linux 软件包管理器 -- yum)
[1. 什么是yum?](#1. 什么是yum?)
[2. 怎么使用yum来下载软件?](#2. 怎么使用yum来下载软件?)
[3. 认识yum源](#3. 认识yum源)
[4. 安装一些有趣的软件](#4. 安装一些有趣的软件)
[二、Linux编辑器 -- vim](#二、Linux编辑器 -- vim)
[1. 什么是vim?](#1. 什么是vim?)
[2. vim的模式切换](#2. vim的模式切换)
[3. vim 各个模式的命令集合](#3. vim 各个模式的命令集合)
[4. vim 的配置](#4. vim 的配置)
[三、Linux编译器 -- gcc/g++](#三、Linux编译器 -- gcc/g++)
[1. 简单认识gcc/g++](#1. 简单认识gcc/g++)
[2. gcc/g++编译代码的过程](#2. gcc/g++编译代码的过程)
[3. 链接的库文件是什么?](#3. 链接的库文件是什么?)
[4. 目标文件和库是如何链接的?](#4. 目标文件和库是如何链接的?)
[5. debug 和 release](#5. debug 和 release)
[四、Linux的自动化构建工具 -- make/makefile](#四、Linux的自动化构建工具 -- make/makefile)
[1. 什么是make/makefile?](#1. 什么是make/makefile?)
[2. 认识依赖关系和依赖方法](#2. 认识依赖关系和依赖方法)
[3. 为什么不能重复使用make指令?](#3. 为什么不能重复使用make指令?)
[4. 如何清理?](#4. 如何清理?)
[5. make/makefile的拓展知识](#5. make/makefile的拓展知识)
[五、Linux的调试工具 -- gdb](#五、Linux的调试工具 -- gdb)
[1. 进入/退出调试模式](#1. 进入/退出调试模式)
[2. 如何使用 gdb ? (由浅入深理解)](#2. 如何使用 gdb ? (由浅入深理解))
[3. gdb使用到的指令汇总](#3. gdb使用到的指令汇总)
一、Linux 软件包管理器 -- yum
1. 什么是yum?
最简单的理解,yum就是用来下载软件的 ,相当于就是Linux 系统(比如 CentOS、RedHat 等)里的" 应用商店 / 软件管家"。
在Linux下安装软件,主要有三种方法:
- 源代码安装:适用于对性能有极致要求的场景,但极其麻烦,耗时很长,容易出错
- rpm安装:存在依赖问题,如果这个软件需要依赖其他软件,RPM 不会自动帮你下,它会报错并停止安装。你需要自己去一个个找这些依赖包。
- yum安装:解决安装源(不需要你到处找下载链接,YUM 知道去哪个服务器(仓库)下载), 安装版本(自动帮你选择适合你系统的软件版本,不会装错),安装依赖(它会自动分析这个软件需要哪些"配角"软件,并自动把它们全部下载安装好),从而自动安装符合当前系统版本的软件。
那么我们通过yum下载一个软件的过程如下:
- 首先,由官方或社区把常用的软件提前编译好,做成软件包 (可以理解成Windows上的安装程序),放在一个远程服务器上。
- 然后,当我们想要下载软件时,yum(就像手机里的应用商店) 会先去服务器上获取软件列表。
- 最后,当我们下达安装指令,yum 就会通过网络去服务器上查找这个软件,把它下载下来,并自动安装到你的系统中。
2. 怎么使用yum来下载软件?
首先,我们先来认识一个软件 lrzsz ,它可以通过 rz 指令(Windows文件传递到Linux上)和sz 指令(Linux文件传递到Windows上)实现Linux与Windows系统之间的文件互传。下面我们就通过安装这个软件来认识一下yum是怎么使用的?
注意:关于 yum 的所有操作必须保证主机(虚拟机)是联网的(可以通过 ping 指令验证 :ping www.baidu.com)。
(说明:由于当前我还不能使用sudo,所以以下的所有操作都是在root账号下进行的,关于sudo我们后续再说)
关于yum的命令,常用的有以下几种:
| 需求 | 命令格式 | 示例 | 说明 |
|---|---|---|---|
| 查找软件 | yum search 关键词 | yum search xxx | 搜索一个名称中存在xxx的软件,当你不确定软件全名时,用它来搜。 |
| 下载安装 | yum install 软件名 | yum install xxx -y | 安装一个叫xxx的软件,加上 -y 参数可以自动确认安装,如果不加-y,则每一次都需要确认是否安装。 |
| 卸载软件 | yum remove 软件名 | yum remove xxx -y | 卸载软件xxx,yum 会自动处理相关的依赖文件。同上,-y不加,则需要确认是否卸载,加上可以自动确认。 |
除此之外,常用的是 yum list ,直接输入 yum list,会将所有软件都列表显示出来,而我们也可以让它和管道一起使用来实现查找的效果。比如我们需要查找软件 lrzsz,基于可以使用指令:
bash
yum list | grep lrzsz
如图所示:
找到后,我们就可以安装了(如果已经安装了,也会提示已经安装了这个软件),假设我们这里没有安装,则安装这个软件 呈现出的效果如图所示:
如果要删除这个软件 ,则呈现出的效果如图所示:
对于安装和卸载,如果不想被询问,则只需要在其指令后面加一个 -y 选项就行了。
注意事项:
- 安装软件时由于需要向系统目录中写入内容, 一般需要 sudo 或者切到 root 账户下才能完成。
- yum安装软件只能一个装完了再装另一个。正在yum安装一个软件的过程中,如果再尝试用yum安装另外 一个软件,yum会报错。
3. 认识yum源
当我们进行yum的安装和查找软件的时候,yum 是怎么找到我们需要的软件的呢?这是因为我们系统中内置了这些软件的下载链接,这下载链接一般在系统的 /etc/yum.repos.d/ 目录下。这就是yum源。
所以,yum 源本质上是一个配置文件(通常在**/etc/yum.repos.d/**目录下),里面记录了一堆网址,而我们通过yum安装软件就是通过系统中的yum源中的网站中去找来下载的。
我们可以打开这个目录看一下,如图所示:
这里的 CentOS-Base.repo 和 epel.repo 中就包含了许多的网站地址等等,用于yum进行访问下载软件。
- 其中的 CentOS-Base.repo 就相当于我们手机或电脑上的应用商店,它是系统自带的,被称为官方yum源;
- 而这里的 epel.repo 中则包含了很多官方源未收录的常用软件,被称为扩展yum源;
注意:在有些系统,可能没有配置完全或者没有扩展yum源,那么我们就需要自己配置一下扩展yum源,执行一下指令即可:
bash
yum install epel-release -y
4. 安装一些有趣的软件
初学Linux,我们可以安装一下有趣的软件来玩玩。
比如:
(1)sl :会跑火车的终端
安装:
bash
yum install sl - y
通过直接输入 sl 就可以运行,其效果如图所示:
(2)cowsay & cowthink:会说话的牛
安装:
bash
yum install cowsay -y
使用:

当然还有许多其他的软件,大家可以在网上搜索即可。那么到这里关于yum的介绍就到此为止了。
二、Linux编辑器 -- vim
1. 什么是vim?
vim 其实就是一种文本编辑器,是一种多模式的编辑器。它的核心是用文本编写,常被用于写代码和修改系统配置文件。
2. vim的模式切换
vim的模式有好多种,最常用的有三种,分别是命令模式(command mode)、插 入模式(Insert mode)和底行模式(last line mode)。
我们先来看一个简单的操作:
当我们使用vim进入一个文件写代码时,刚进入的默认情况下就是命令模式(在该模式下,与用户所有的输入都会被当做命令),想要写代码,则需要通过按【i】命令切换到插入模式,写完代码后,要退出,首先需要按【Esc】回退到命令模式,然后再切换到底行模式,在底行模式中先按【shift + ;】(其实就是输入【:】),在输入wq 就可以保存退出了(其中w是保存,q是退出),这就是vim操作的基本逻辑,它们的关系如图所示:
命令模式(默认模式):
插入模式:
底行模式:
3. vim 各个模式的命令集合
(1)命令模式
光标移动
- 【gg】:定位光标到最开始行的开头。
- 【shift + g】:其实就是【G】,定位到最后一行的的开头。
- 【n + shift + g】或【n + gg】:定位到第 n 行。
- 【shift + 6】:即【^】,定位光标到当前行的开头。
- 【shift + 4】:即【$】,定位光标到当前行的结尾。
- 【h】、【j】、【k】、【l】:光标向左(h)、下(j)、上(k)、右(l)进行移动。
- 【w】、【b】:光标安装单词的长度进行行内或跨行移。w为向前,b为向后。
复制粘贴
- 【yy】:复制光标所在行。【n + yy】表示复制光标所在的向后 n 行。
- 【p】:在光标位置粘贴 1 次所复制的内容。【n + p】是在光标位置粘贴 n 次所复制的内容。
- 【dd】:剪切当前行(删除当前行)。【n + dd】表示剪切(删除)从当前行开始的往后一共 n 行。
- 【u】:撤销一次操作。
- 【Ctrl + r】:撤销之前的撤销。
其他命令
- 【shift + ~】:按一下表示只转换一个字符的大小写,一直按表示从光标位置开始向后一直转换大小写。
- 【r + 目标字符】:替换光标所在位置的字符为目标字符。【n + r + 目标字符】表示替换光标位置以及之后的一共n个字符为目标字符。
- 【x】:对光标位置的字符做删除。【n + x】:对光标所在位置之后的n个位置做删除。
- 【shift + r】:转换为替换模式(第四种模式,按【Esc】退回到命令模式),可以对内容进行整体替换。
- 【Ctrl + w + w】:表示在分屏操作是让光标切换窗口。
(2)底行模式
必须在底行模式中进行输入以下命令:
- 【set nu】:在每一行的最前面显示出文件中的每一行的行号。
- 【set nonu】:去掉行号。
- 【数字】:在冒号后输入一个数字,再按回车键就会跳到该行了,比如输入数字15, 再回车,就会跳到文章的第15行。
- 【/关键字】:先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按 「n」会往后寻找到您要的关键字为止。
- 【w】:保存文件。【w!】表示强制保存文件。
- 【q】:退出文件。【q!】表示强制退出文件。一般建议和[【w】一起使用,即【wq】表示在退出的时候还可以保存文件。
- 【vs 新文件名】:表示分屏操作,即同时显示当前文件,也显示新文件对应的内容。
除此之外,在底行模式中,还可以在不突退出vim时,执行外部命令,比如使用 【gcc code.c】来编译当前文件,或这使用【./a.out】来运行代码,使用【ls】等等。
注意:当我们使用vim打开了一个不存在的文件时,如果我们没有在这个文件使用【w】,则这个文件是不会被创建的;如果使用了【w】,那么不论你是否写了内容,这个文件都会被创建。
4. vim 的配置
vim如果不配置,那么我们在写一些文本的时候就会感到不方便,就比如,我们写代码的时候没有提示一样。如果想要我们使用vim写代码时能够有一些提示,让我们更加方便。我们就需要配置一下vim。
手动配置方法:
那么vim怎么配置呢?其实很简单,在我们每一个用户的家目录 下一般都有一个.vimrc的文件,如果没有,则创建一个。如图所示:
我们要配置vim,其实就是对 .vimrc 这个文件进行编辑。
我们来看看编辑了这个文件的效果:

因为在vim中set nu在本来就是给内容数字行号的,不做任何配置,在默认情况下vim打开的内容中是不会有行号的,这里将它放在 .vimrc 中,就相当于是给vim配置成了默认情况下就有行号了。
关于其他配置的方法也是一样,给vim配置,基本上就是编辑 .vimrc 中的内容。但是这种需要我们自己去手动配置,比较麻烦。
这里有一种方便的自动配置方法 :VimForCpp: 快速将vim打造成c++ IDE
打开链接,找到如下信息,工具它的提示进行安装即可。
执行完这个命令后,得到如下界面:
执行 "source ~/.bashrc" 或者重启终端, 使 vim 配置就生效了。
一键配置完成后,会产生一个install.sh文件,它里面包含了安装时采用的插件等配置,建议将它设为隐藏文件,不建议删掉。
最后:对于vim的配置,我们不建议给root配置,root的.vimrc是在/etc/目录下,配置它用容易出错。而对于普通用户进行配置,它只会影响当前自己的用户,不会影响其他普通用户。所以建议使用普通用户来配置vim。
三、Linux编译器 -- gcc/g++
1. 简单认识gcc/g++
简单来说,它们都是编译器。
- gcc 是用于编译C语言代码的
- g++ 是专门用于编译 C++ 语言
2. gcc/g++编译代码的过程
在Linux下使用gcc或g++的格式如下所示: 格式 gcc [选项] 要编译的文件 [选项] [目标文件],这种写法是标准写法。
下面以 gcc 为例来理解,而g++和gcc的选项都是一样的,可以类似理解。
当我们要让一个C语言源代码那个形成可执行程序,则要经过4个阶段:
1. 预处理阶段
使用 -E 选项。预处理的功能主要包括宏定义,文件包含,条件编译,去注释等。然后生成一个 .i 文件,执行预处理阶段的指令如下:
bash
gcc -E test.c -o test.i
其中 -E 选项的意思就是:告诉 gcc,从现在开始进行程序的翻译,将预处理工作做完就停下来,不要往后走了!
而 -o 选项则表示指定输出文件,即预处理后会形成的文件。
2. 编译阶段
使用 -S 选项。在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 会把代码翻译成汇编语言,形成一个 " .s " 文件。执行该阶段的指令如下:
bash
gcc -S test.i -o test.s
其中 -S 选项的意思就是:告诉 gcc,从现在开始进行程序的翻译,将编译工作做完就停下来,不要往后走了!
而 -o 选项则表示指定输出文件。
3. 汇编阶段
使用 -c 选项。汇编阶段是把编译阶段生成的 " .s " 文件转成目标文件。 执行该阶段的指令如下:
bash
gcc -c test.s -o test.o
其中 -c 选项的意思就是:告诉 gcc,从现在开始进行程序的翻译,将汇编工作做完就停下来,不要往后走了!
而 -o 选项则表示指定输出文件。
其中生成的 " .o " 文件是指可重定位目标二进制文件 ,简称目标文件。在Linux下是 .o 文件,而在Windows下则是 .obj 文件。此时的模板文件不可以独立执行,虽然是二进制文件,但是还需要经过 链接 过程才能执行。
4. 链接阶段
在链接阶段中则会将目标文件和库文件链接形成一个可执行文件。使用指令为:
bash
gcc test.o --o test
这里的 test 就是一个可执行文件,我们通过**./test**就可以执行这个C语言代码运行的结果了。
3. 链接的库文件是什么?
比如以下这个C源代码:
我们知道,在预处理阶段的时候,我们会将代码的头文件<stdio.h>展开,但<stdio.h>中也只有该函数的声明,而 没有定义函数的实现,那么"printf" 函数的实现又在哪里呢?
答案是:系统把这些函数实现都被放到名为 libc.so.6 的库文件中去了,而在链接的时候,gcc 则会自动到系统的 /usr/lib/ 目录(64位系统是 /usr/lib64/ )在去查找这个库文件,去链接实现了这个函数的库文件,从而生成最终的可执行文件。
注意:这里提到的 .so 文件是动态库(关于动 / 静态库等会解释)。
所以,库文件就是给我们提供方法实现的文件。而库文件有分为动态库和静态库。
- 在Linux中,动态库的文件后缀是:.so ,而静态库的文件后缀则是 .a 。
- 在Windows中,动态库的文件后缀是:.dll ,而静态库的文件后缀则是 .lib 。
以动态库为例,它的命名方式是:lib+[名称]+.so.+[版本号]。 比如上面 gcc 链接 libc.so.6 去掉lib和后面的.so.6后只剩下一个c,表示的就是C库。所以在链接时, gcc 就会链接自己的库。
可以看看系统的 /usr/lib64/ 目录中的文件:
可以看到其中确实存在了许多库文件。
4. 目标文件和库是如何链接的?
目标文件和库的链接可以分为两种情况:动态链接 和静态链接。
动态链接 其实就是将目标文件和动态库 进行链接。
而动态库 又叫共享库 ,是有是有很多程序共享的,所以动态库不能缺失,一旦对应的动态库缺失,影响的不止一个程序,可能导致很多程序都无法进行正常运行 !
这就就相当于一个网吧(动态库),一旦这个网吧没有了,就会导致很多人(程序)都无法使用电脑上网一样。
静态链接 其实就是将目标文件和静态库 进行链接。
在编译器使用静态库进行静态链接的时候,会将自己的方法拷贝到目标程序中,该程序以后不用再依赖静态库!
就相当于是电脑(库代码)买回家(拷贝进程序),那么在家就可以上网了,不需要再去网吧了。
验证
在Linux下,编译形成可执行程序,默认采用的就是动态链接 。我们可以通过一个指令 ldd 来查看( ldd 指令可以查看一个可执行文件所依赖的动态库)如下所示:
下面我们来看看静态链接,首先要实现静态链接,我们的系统中一定要有静态库,按照静态库的指令如下所示:
安装C语言的静态库,指令(在root下则不用写sudo)如下:
bashsudo yum install -y glibc-static安装C++语言的静态库,指令(在root下则不用写sudo)如下:
bashsudo yum install libstdc++-static
有了静态库,我们就可以实现静态链接了,相比于默认链接,静态链接的指令还需要加上一个**-static**选项,如图所示:
同时我们也可以发现,静态链接的文件大小相比于动态链接,其大小是大了很多很多的,这也是静态链接的一个缺点。
gcc 编译器在动态链接和静态链接之间是如何做选择的呢?以及 -static 参数到底起了什么作用?
- 如果我们没有静态库,那么 -static 就不能使用(即编译失败)。
- 如果我们没有动态库,只有静态库,那么 gcc 默认会优先动态链接,所以 -static 的本质其实就是 改变优先级,并且只适配一次(即使用了 -static 选项则所有链接要求则都会变成静态链接)。
最后,我们来比较一下动态链接和静态链接之间的优缺点:
对于动态链接:
- 优点:动态库因为是共享库,有效的节省资源(磁盘空间(生成可执行文件体积小),内存空间(可执行程序会加载到内存中,变向节省空间),网络空间(下载程序的体积小)等)
- 缺点:依赖性强。动态库一旦缺失,导致各个程序都无法运行。
对于静态链接:
- 优点:静态库,不依赖库,程序可以独立运行
- 缺点:体积大,比较消耗资源(同上,即磁盘空间,内存空间,网络空间)。
5. debug 和 release
简单来说,debug版本就是调试版,可以被追踪同时,就是来方便我们程序员调试的;而release版本则是发布版,是给用户使用的。
以debug版本形成可执行程序,它的体积会比以release版本形成的可执行程序体积大,因为在debug版本下,可执行程序是被添加了大量的符号表信息,方便调试器取调试的。在Linux下,我们可以来验证这里一点。
注意:在Linux下,默认的是动态链接,然后以release版本形成的可执行程序的;如果要形成debug版本的可执行程序,则还需要加上 -g 选项。如图所示:
可以发现debug版本的可执行程序大小比 release 版本的可执行程序要大一点,这就是debug版本添加的调试信息。我们可以验证一下。
验证
可执行程序也有自己的二进制格式,即ELF格式,那么我们就可以使用 readelf (readelf 正是用来直接查看 ELF 文件内部结构的工具)来查看,需要使用的选项是 -S 选项来读取程序的二进制构成,如下所示:
这就是多的那部分信息。
四、Linux的自动化构建工具 -- make/makefile
1. 什么是make/makefile?
认识它是解决什么问题的?
当我们要按照魔种规则编译多个源文件时,如果我们自己直接通过 gcc 这样的指令来进行时,往往会变得非常繁琐且容易出错。而 make 和 makefile 则可以完美地解决了像这样的问题。
它们带来的好处就是------"自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
明确概念
我们要明确make和makefile是两个不同的东西。
- makefile :是一个文件。我们写的时候,不用关系首字母大写,即Makefile也是一样的效果。
- make :是一个指令。是一个解释makefile中指令的命令工具
make是一条命令,makefile是一个文件,通过这两个搭配使用,就可以完成项目自动化构建。
如何使用的呢?
我们可以下面的一个例子来看看它们是如何实现的。
首先我们创建一个makefile文件(首字母大小写不重要),如图所示:
然后,如果我们想要实现通过 make 就可以编译这个mycode.c源文件,那么我们就需要在 makefile 文件中写入以下信息:
注意:其中第二行的gcc前面一定要是 【Tab】。如果写4个空格都是那错的
这里我们简单解释一下:
- 第一行表示依赖关系。其中的mycode表示想要生成的目标文件,然后通过冒号隔开,后面的mycode.c表示的是生成目标文件所需要依赖的文件。
- 第二行表示依赖方法,首先是 [Tab] ,其后面跟着的就是想要依赖 mycode.c 生成mycode的具体指令。
然后我们就可以通过调用make指令来实现编译 mycode.c 代码了,使用效果如图所示:
最后,如果我们要清理生成的可执行程序mycode,则我们就需要先在makefile文件中写入以下信息:
这里我们也解释一下:
- 这里第4行: clean: 也试一下依赖关系,其中的clean的名字我们可以随便取,但一般都是clean来命名表示打扫的意思,它的冒号后面不跟任何东西,表示它不依赖任何文件。
- 第5行也是一个依赖方法:这里的指令表示的就是要删除 mycode 这个文件。这里也是以【Tab】键开头。
然后,我们就可以使用 make clean 指令,直接删除我们编译生成的可执行程序了,如图所示:
2. 认识依赖关系和依赖方法
通过上述一个对 make/makefile 的使用示例,我们也应该已经发现了,它们的使用其实很简单,就是:
- 先在makefile中写上依赖关系和依赖方法。
- 然后就可以通过make指令来调用其中的依赖方法,实现对应具体功能了。
注意:依赖关系和依赖方法通常都是一个整体,一般不要分开写,不让会导出错(比如出现指令覆盖等问题)。
make的工作原理
那么如果有多个依赖关系和依赖方法呢?
比如如果我们要实现代码翻译的全过程,则我们的makefile文件中就需要写如下信息:
当我们使用make指令的时候,make会在当前目录下找名字叫"Makefile"或"makefile"的文件。 如果找到,它会找文件中的第一个目标文件, 并把这个文件作为最终的目标文件。
想如上信息写的话,make会从上到下来依次扫描makefile文件,首先遇到mycode依赖mycode.o生成,则会以mycode作为最终目标文件,但是没有mycode.o,则就会继续扫描去找生成mycode.o的依赖关系,但是mycode.o又是依赖mycode.s的,而mycode.s在当前目录下也没有,则又会去找生成,mycode.s的依赖关系,依次类推,直到找到依赖于存在当前目录下的mycode.c源文件,在张图中,它们就会根据依赖关系来依次执行各个依赖方法。需要注意这种寻找是根据依赖关系找的,所以并不是在makefile文件越在前越先执行。
所以,make对makefile的扫描顺序是从上往下读文件(为了建立依赖关系图),而各个指令执行顺序是根据依赖链条来决定先做什么的,是一种自动推导的过程 ---- 这就是makefile的自动化推导。
而在我们使用的时候,这些指令的指向顺序也会显示出来的,如图所示:
最后,虽然上述为了编译,是将整个翻译过程都写出来了的,但在实际开发中,我们通常不会写得这么啰嗦 ,应该是能简化就简化。
注意:如果有多个依赖关系是相互依赖的,我们一定不要漏掉其中的任何一个依赖的关系和方法。
3. 为什么不能重复使用make指令?
当我们使用make的时候,我们应该也发现了,我们并不能连续重复的使用make指令,这为什么呢?
其原因如下:
这是因为文件没有被更新过,所以就不会让你再编译了。再编译时没必要的,如果能够一直重复编译,那么当遇到代码量很大的时候,编译速度慢,允许重复编译会降低效率。
所以,不让你重复编译,主要是因为提供编译效率。
但是,如果我们修改了源文件中的一点点内容,那么make又可以使用了。如图所示:
下面我们来理解一下它是怎么做到的?
因为编译代码,一定是先有源文件,再才会有可执行程序的,而阻止你重复编译其实就是通过比较源文件和可执行程序的更新时间来决定的,如果源文件的更新时间比可执行程序的更新时间新,那就可以编译。
拓展:认识文件的时间属性
通过 stat + [文件名] 指令可以来查看文件的状态的。通过它,我们也可以查看到文件的三个重要的时间属性,如图所示:
这三个时间属性的解释如下:
- Access:访问时间,一般来说任何操作都会改变这个时间,但是在现代Linux中并实时更新这个时间了,因为现代Linux系统为了性能优化,默认不实时更新Access时间,避免因频繁读写磁盘拖慢编译速度,而是根据 Modify 或 Change 的更新情况(比如达到一定次数等)来决定。
- Modify:修改时间,修改文件内容(如 vim 保存、写入数据)的时间。
- Change:改变时间,修改文件属性(如 chmod 改权限、mv 改名)的时间。
注意:文件 = 文件内容 + 文件属性
而上述源文件的更新时间和可执行程序的更新时间的比较其实就是比较的 Modify。
如果仍然想让它可以重复编译,那么我们就可以在这段依赖规则前面加上**.PHONY:[目标文件名]**,如图所示:
使用效果:
这是只是一种解决不能重复使用make的方法,但是我们不建议这样做。
4. 如何清理?
关于make的清理,其实就是我们在makefile中的一条依赖关系和依赖方法,只是这个依赖关系不需要依赖任何文件,即冒号后面是空的,如图所示:
这里的依赖关系的目标文件的名字我们其实可以随便命名的,而这里其中的 clean 只是我们方便用于表示打扫的意思。
注意事项1:不要放在makefile的最前面
但是需要注意,清理的依赖规则一般建议不要放在最前面,因为make默认会从上向下扫描文件,会把第一次遇到的目标文件作为默认动作,比如这样写:
那么使用make的行为就是删除可执行程序mycode,而不是指向编译动作了。
注意事项2:为了更严谨,通常建议加上 .PHONY:[目标文件名]
虽然make不能重复执行,但是我们的清理工作是是非常建议总是执行的,所以加上 .PHONY 后,就可以在告诉 make 工具,clean 是一个伪目标,它不代表任何真实的文件。,可以总是被执行,如图所示:
这才是清理工作比较严谨的写法。
5. make/makefile的拓展知识
第一点:在makefile中我们有两个特殊符号:
- $@ :表示目标文件。
- $^ :表示依赖文件。
使用示例:
第2行的意思和第3行的意思是一样的。
第二点:不让我们使用make指令时出现回显指令
默认情况下,每当我们调用make指令,终端都会将这个make执行的指令打印一行出来,如图所示:
如果不想让它显示,则需要再makefile文件中的依赖方法前面加上一个 @ 符号,makefile信息修改如图所示:
使用效果:
五、Linux的调试工具 -- gdb
在Linux下,我们调试代码的工具是 gdb 。之前我们知道程序的发布方式分为了debug模式和release模式,而我们使用gcc/g++默认编译出来的代码都是以release模式发布的,如果要调试,则需要以debug模式发布的程序,因为只有debug模式发布的程序才有调试信息,才可以调试。
如果要以debug模式发布,则我们在编译代码时使用的指令还需要在 后面加一个 -g 选项。
那么我们的 gdb 是怎么使用的呢?简单来说,就是先通过gdb进入调试模式,然后输入gdb中的一系列指令来实现代码的调试。下面我们来认识一下它的使用方法:
1. 进入/退出调试模式
进入调试模式其实就一句指令:gdb + 程序名,它的作用就是启动调试,加载程序
首先,我们需要一个以debug模式发布的程序,我们可以通过一下两个文件来得到这个程序,如图所示:
然后,对于这里的mycode.c源文件,我们就通过make直接编译得到 mycode 的可执行程序了,如图所示:
然后我们就可以通过gdb mycode指令,就可以进入mycode 的调试模式了,如图所示:
出现这样的提示就说明你已经进入了调试模式了。
如果你的系统没有装 gdb,则就需要去安装一个,安装方法如下:
如果要退出gdb的调试模式,则可以在调试模式下输入 quit 或者简写的 q 命令,就可以退出了,如图所示:
当然 【Ctril + D】也可以退出。
最后要注意:**gdb 是"无状态"的。**一但我们退出了gdb后,本次会话中设置的所有断点、观察点以及手动修改的变量值都会丢失,当我们再次使用gdb时,我们曾经进入gdb的调试痕迹都会被自动清理掉,你需要重新设置断点和环境。
2. 如何使用 gdb ? (由浅入深理解)
通过 gdb 进入调试模式后,要进行调试,是通过一些指令实现的。这些指令有很多,下面我们逐步来一一介绍,大概会介绍 17 条(建议这里依次向下慢慢看,可以由浅入深来理解)。
(1)显示源代码:list
list命令的作用就是 显示程序的源代码,默认只会显示10行。list可以简写为 l ,它们是完全等效的。
写法:
- list 或 l ,继续显示上一次 list 之后的代码(通过一直回车,可以实现向下翻页来显示代码)
- list 行号 ,表示显示上一次 list 之前的代码(通过一直回车,可以实现向上翻页)。
- list 行号 ,表示显示以该行号为中心的上下文代码;
- list 函数名 ,表示显示指定函数的源代码。
- list 文件名:行号 ,冒号分开。表示显示指定文件中某行的代码(用于多文件项目)
- list 开始位置的行号, 结束位置的行号 ,逗号分开表示显示指定范围内的代码。
如果要显示所有代码 ,常用的方式就是先输入 l 0 或 list 0 ,然后一直回车即可,这就是向下翻页,如下所示:
这里一直回车就可以显示出代码的原因是:
因为gdb会自动记录最近的指令,还会默默记录下当前显示到了第几行。当你输入 list 0 时,显示了前 10 行后,再按回车,这里它检测到上一次是 list 相关的命令,之后每按一次回车,gdb 就会从记录的那个位置接着往下显示下一批 10 行代码,直到文件结束。
(2)启动当前程序:run
run指令表示的意思是启动当前程序,也可以在我们单步执行的时候重新启动程序,如果当前程序中没有设置断点,那么它就会将整个程序都指向完。
run 指令也可以直接简写为 r ,它们的效果是一样的。使用效果如下所示:
(3)设置断点:break
break的作用就是设置断点,使用方法为: break [行号] 或者简写为 b [行号] ,使用效果如图所示:
还可以对函数这是断点,即 p [函数名] ,运行时,会在函数的第一行代码停下。
当然,如果是多文件的话,也可以进行跨文件打断点,方法如下:
- p [文件名:行号] :在对应文件的行号位置打断点。
- p [文件名:函数名] :在对应文件的函数的第一行位置打断点。
(4)查看断点:info break
info break 我们可以简写为 info b ,所以查看我们在当前程序中所打的断点,就一句指令即:info b,使用效果:
通过info b 我们可以看到6信息,下面我们解释一下:
- Num: 断点编号。
- Type:断点类型。
- Disp:命中后的行为(暂时可以不关心)。
- Enb:表示断点是否启用了。如果正在生效,则其状态就是y;如果被禁用状态就是 n。
- Address:表示断点在内存中的地址。
- What:表示断点在代码的什么位置。
另外还有一点关于info b的内容效果在后面的 "(6)单步逐过程执行:next " 中
(5)删除断点:delete
删除断点的指令一般为 delete [断点编号] ,也可以简写为 d [断点编号] ,注意这里不是行号,而是通过info b指令显示出来的断点编号。使用效果:
注意:删除断点后,其他断点的编号仍然是不会改变的。
(6)单步逐过程执行:next
next 指令可以简写为 n ,表示单步逐过程执行,不进入函数内部。使用的前提是程序必须启动了。那么我们可以来是使用一下看看:
当我们这样执行完一次代码后,我们再通过info b指令查看,会发现,每个断点信息后面多个一行:
它表示的是断点的命中次数。
(7)单步逐语句执行:step
step 指令可以简写为 s ,表示单步逐语句执行,会进入函数内部。它的效果可以类比next,只是step执行的时候不会跳过函数而已。
(8)查看变量内容:print [变量]
print [变量] 可以简写为 p [变量] ,它们可以用来打印对应变量的值,来达到查看变量内容的效果。
使用效果:
但是这种查看变量值的方法并不能一直显示,必须通过p指令才能显示。如果要实现在我们在执行代码的过程中一直都显示变量,则我们需要通过 undisplay [变量] 来实现。
(9)常显示一个变量:display [变量]
display [变量名] 可以跟踪查看一个变量,当我们单步执行代码(即使用n或s指令)时,每次停下来都会显示通过display设置了的变量的信息。如图所示:
其中每个变量前的编号表示的就是一直显示变量的编号。
如果我们要取消这种显示,则需要使用 undiaplay [一直显示变量的编号] ,使用效果如图所示:
(10)运行至指定行:until [行号]
当我们如果在单步执行的时候遇到一个比较大的循环时,我们如果一直使用n或s来执行很多次来运行完循环,这是不明智的。而在gdb中我们可以通过一个指令 until [行号] 来快速运行完这个循环。
所以 until [行号] 是可以达到快速跳转运行代码的目的的。
(11)执行到当前函数:finish
当我们进入一个函数,想要快速将这个函数运行完,则我们就可以使用 finish 指令来实现。
(12)运行到下一个断点:continue
continue 指令,可以简写为 c ,表示继续运行,直接跳到下一个断点。
(13)禁用断点:disable [断点编号]
disable [断点编号] 指令可以用于禁用断点,它会将我们使用info b显示的End属性改为 n ,如图所示:
(14)启用断点:enable [断点编号]
enable [断点编号] 指令可以用于重新启用断点,它会将我们使用info b显示的End属性改为 y ,如图所示:
(15)修改变量的值:set var [变量] = [值]
set var 是专门用来在调试过程中强行修改变量的值 。使用方法为 set var [变量] = [值] 。如图所示:
这里表示将sum强制修改为666666。
(16)查看函数调用情况及参数:backtrace
backtrace 可以简写为 bt 用于查看当前程序函数调用的情况,也就是函数调用的堆栈。比如以下代码:
(17)查看当前函数栈帧中的所有局部变量:info locals
info loacls 指令可以一次性列出当前函数中所有局部变量的名称和当前值。使用效果:
3. gdb使用到的指令汇总
以下是如何使用gdb的指令汇总。具体介绍在上面。
gdb调试的基本指令
- (1)显示源代码:list
- (2)启动当前程序:run ,简写 r
- (3)设置断点:break ,简写 b
- (4)查看断点:info break ,简写 info b
- (5)删除断点:delete [断点编号],简写 d [断点编号]
- (6)单步逐过程执行:next ,简写 n
- (7)单步逐语句执行:step ,简写 s
gdb用于监视变量的指令
- (8)查看变量内容:print [变量] ,简写 p [变量]
- (9)常显示一个变量:display [变量]
帮助定位问题位置范围的常用指令
- (10)运行至指定行:until [行号]
- (11)执行到当前函数:finish
- (12)运行到下一个断点:continue
- (13)禁用断点:disable [断点编号]
- (14)启用断点:enable [断点编号]
其他
- (15)修改变量的值:set var [变量] = [值]
- (16)查看函数调用情况及参数:backtrace ,简写 bt
- (17)查看当前函数栈帧中的所有局部变量:info locals
到这里,我们对Linux中几个工具:yum,vim,gcc/g++,make/makefile,gdb有了一定的理解了,而已能完成Linux环境中一下基本的开发使用了。其实在Linux中,还有一个工具git,虽然本章并未提即,但后续我会单独写一个关于git的专题,以此来做介绍。
感谢各位观看!希望大家多多支持!

执行完这个命令后,得到如下界面:
执行 "source ~/.bashrc" 或者重启终端, 使 vim 配置就生效了。

注意:其中第二行的gcc前面一定要是 【Tab】。如果写4个空格都是那错的



