一. 认识编辑器VIM
vi/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,⽽且还有⼀些新的特性在⾥⾯。例如语法加亮,可视化操作不仅可以在终端运⾏,也可以运⾏于x window、 mac os、 windows。我们课堂上,统⼀按照vim来进⾏讲解。
1.1 vim的基本概念
本篇文章上我们讲解vim的三种模式(其实有好多模式,⽬前掌握这3种即可),分别是命令模式 (command mode)、插⼊模式 (Insert mode)和底⾏模式(last line mode),各模式的功能区分如下:
正常/普通/命令模式(Normal mode):
控制屏幕光标的移动,字符、字或⾏的删除,移动复制某区段及进⼊Insert mode下,或者到 last line mode
插⼊模式(Insert mode):
只有在Insert mode下,才可以做⽂字输⼊,按「ESC」键可回到命令⾏模式。该模式是我们后⾯⽤的最频繁的编辑模式。
末⾏模式(last line mode):
⽂件保存或退出,也可以进⾏⽂件替换,找字符串,列出⾏号等操作。
在命令模式下,*shift+:* 即可进⼊该模式。要查看你的所有模式:打开 vim,底⾏模式直接输⼊ :help vim-modes

1.2 vim的基本操作
进⼊vim,在系统提⽰符号输⼊vim及⽂件名称后,就进⼊vim全屏幕编辑画⾯:
$ vim test.c
不过有⼀点要特别注意,就是你进⼊vim之后,是处于[正常模式],你要切换到[插⼊模式]才能够输⼊⽂字
正常模式\]切换⾄\[插⼊模式\]: > 输⼊a > 输⼊i 输⼊o \[插⼊模式\]切换⾄\[正常模式\]: ⽬前处于\[插⼊模式\],就只能⼀直输⼊⽂字,如果发现输错了字,想⽤光标键往回移动,将该字删除,可以先按⼀下「ESC」键转到\[正常模式\]再删除⽂字。当然,也可以直接删除 正常模式\]切换⾄\[末⾏模式\]: 「shift + ;」, 其实就是输⼊ ":" 退出vim及保存⽂件,在\[正常模式\]下,按⼀下「:」冒号键进⼊「Last line mode」,例如: > : w (保存当前⽂件) > : wq (输⼊「wq」,存盘并退出vim) > : q! (输⼊q!,不存盘强制退出vim) ### 1.3 vim正常模式命令集 插⼊模式: > 按「i」切换进⼊插⼊模式「insert mode」,按"i"进⼊插⼊模式后是从光标当前位置开始输⼊ > ⽂件; > 按「a」进⼊插⼊模式后,是从⽬前光标所在位置的下⼀个位置开始输⼊⽂字; 按「o」进⼊插⼊模式后,是插⼊新的⼀⾏,从⾏⾸开始输⼊⽂字 从插⼊模式切换为命令模式: 按「ESC」键。 移动光标: > 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⾏ ⾸。 ### 1.4 vim末行模式命令集 在使⽤末⾏模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进⼊末⾏模式。 列出⾏号: 「set nu」: 输⼊「set nu」后,会在⽂件中的每⼀⾏前⾯列出⾏号。 跳到⽂件中的某⼀⾏: 「#」:「#」号表⽰⼀个数字,在冒号后输⼊⼀个数字,再按回⻋键就会跳到该⾏了,如输⼊数字15,再回⻋,就会跳到⽂章的第15⾏。 查找字符: > 「/关键字」: 先按「/」键,再输⼊您想寻找的字符,如果第⼀次找的关键字不是您想要的,可以 ⼀直按「n」会往后寻找到您要的关键字为⽌。 「?关键字」:先按「?」键,再输⼊您想寻找的字符,如果第⼀次找的关键字不是您想要的,可以⼀直按「n」会往前寻找到您要的关键字为⽌。 保存⽂件: 「w」: 在冒号输⼊字⺟「w」就可以将⽂件保存起来 离开vim: > 「q」:按「q」就是退出,如果⽆法离开vim,可以在「q」后跟⼀个「!」强制离开vim。 > 「wq」:⼀般建议离开时,搭配「w」⼀起使⽤,这样在退出的时候还可以保存⽂件。 ### 1.5 vim操作总结 三种模式: > 正常模式 > 插⼊模式 > 底⾏模式 vim操作: > 打开,关闭,查看,查询,插⼊,删除,替换,撤销,复制等等操作。  ### 1.6 简单vim配置\[了解
配置⽂件的位置
在⽬录 /etc/ 下⾯,有个名为vimrc的⽂件,这是系统中公共的vim配置⽂件,对所有⽤⼾都有
效。
⽽在每个⽤⼾的主⽬录下,都可以⾃⼰建⽴私有的配置⽂件,命名为:".vimrc"。例如,/root
⽬录下,通常已经存在⼀个.vimrc⽂件,如果不存在,则创建之。
切换⽤⼾成为⾃⼰执⾏ su ,进⼊⾃⼰的主⼯作⽬录,执⾏ cd ~
打开⾃⼰⽬录下的.vimrc⽂件,执⾏ vim .vimrc
常⽤配置选项,⽤来测试
设置语法⾼亮: syntax on
显⽰⾏号: set nu
设置缩进的空格数为4: set shiftwidth=4
使⽤插件
要配置好看的vim,原⽣的配置可能功能不全,可以选择安装插件来完善配置,保证⽤⼾是你要配置的⽤⼾,接下来:
安装TagList插件,下载taglist_xx.zip ,解压完成,将解压出来的doc的内容放到〜/.vim/doc, 将解压出来的plugin下的内容拷⻉到〜/.vim/plugin
在〜/.vimrc 中添加: let Tlist_Show_One_File=1 let
Tlist_Exit_OnlyWindow=1 let Tlist_Use_Right_Window=1
安装⽂件浏览器和窗⼝管理器插件: WinManager
下载winmanager.zip,2.X版本以上的
解压winmanager.zip,将解压出来的doc的内容放到〜/.vim/doc, 将解压出来的plugin下的内容拷⻉到〜/.vim/plugin
在〜/.vimrc 中添加 let g:winManagerWindowLayout='FileExplorer|TagList
nmap wm :WMToggle<cr>
然后重启vim,打开~/XXX.c或〜/XXX.cpp, 在normal状态下输⼊"wm", 你将看到上图的效果。
二 编译器GCC/G++
2.1 背景知识
- 预处理(进⾏宏替换/去注释/条件编译/头⽂件展开等)
- 编译(⽣成汇编)
- 汇编(⽣成机器可识别代码)
- 连接(⽣成可执⾏⽂件或库⽂件)
2.2 gcc编译选项
**格式 gcc [**选项] 要编译的⽂件 [选项] [⽬标⽂件]
2.2.1 预处理(进⾏宏替换)
预处理功能主要包括宏定义,⽂件包含,条件编译,去注释等。
预处理指令是以#号开头的代码⾏。
实例: gcc --E hello.c --o hello.i
选项"-E",该选项的作⽤是让 gcc 在预处理结束后停⽌编译过程。
选项"-o"是指⽬标⽂件,".i"⽂件为已经过预处理的C原始程序。
2.2.2 编译(⽣成汇编)
在这个阶段中,gcc ⾸先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的⼯作,在检查⽆误后,gcc 把代码翻译成汇编语⾔。
⽤⼾可以使⽤"-S"选项来进⾏查看,该选项只进⾏编译⽽不进⾏汇编,⽣成汇编代码。
实例: gcc --S hello.i --o hello.s
2.2.3 汇编(⽣成机器可识别代码)
汇编阶段是把编译阶段⽣成的".s"⽂件转成⽬标⽂件
读者在此可使⽤选项"-c"就可看到汇编代码已转化为".o"的⼆进制⽬标代码了
实例: gcc --c hello.s --o hello.o
2.2.4 连接(⽣成可执⾏⽂件或库⽂件)
在成功编译之后,就进⼊了链接阶段。
实例: gcc hello.o --o hello
2.3 动态链接和静态链接
在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,但是每个源⽂件都是独⽴编译的,即每个*.c⽂件会形成⼀个*.o⽂件,为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。这个链接的过程就是静态链接。静态链接的缺点很明显:
--浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本;
--更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程序。但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在执⾏的时候运⾏速度快。
动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,⽽不是像静态链接⼀样把所有程 序模块都链接成⼀个单独的可执⾏⽂件。
动态链接其实远⽐静态链接要常⽤得多。⽐如我们查看下 hello 这个可执⾏程序依赖的动态库,会发现它就⽤到了⼀个c动态链接库:
cpp
$ ldd hello
linux-vdso.so.1 => (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
# ldd命令⽤于打印程序或者库⽂件所依赖的共享库列表。
在这⾥涉及到⼀个重要的概念: 库
我们的C程序中,并没有定义"printf"的函数实现,且在预编译中包含的"stdio.h"中也只有该
函数的声明,⽽没有定义函数的实现,那么,是在哪⾥实"printf"函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库⽂件中去了,在没有特别指定
时,gcc 会到系统默认的搜索路径"/usr/lib"下进⾏查找,也就是链接到 libc.so.6 库函数中去,这样
就能实现函数"printf"了,⽽这也就是链接的作⽤
2.4 动态库和静态库
静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。其后缀名⼀般为".a"
动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。动态库⼀般后缀名为".so",如前⾯所述的
libc.so.6 就是动态库。gcc 在编译时默认使⽤动态库。完成了链接之后,gcc 就可以⽣成可执⾏⽂件,如下所⽰。 gcc hello.o --o hello
gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证。
⼀般我们的云服务器,C/C++的静态库并没有安装,可以采⽤如下⽅法安装:
cpp
# Centos
yum install glibc-static libstdc++-static -y
# ubuntu
apt install glibc-static libstdc++-static -y
2.5 gcc其他常⽤选项 - 了解即可'
-E 只激活预处理,这个不⽣成⽂件,你需要把它重定向到⼀个输出⽂件⾥⾯
-S 编译到汇编语⾔不进⾏汇编和链接
-c 编译到⽬标代码
-o ⽂件输出到 ⽂件
-static 此选项对⽣成的⽂件采⽤静态链接
-g ⽣成调试信息。GNU 调试器可利⽤该信息。
-shared 此选项将尽量使⽤动态库,所以⽣成⽂件⽐较⼩,但是需要系统由动态库.
-O0
-O1
-O2
-O3 编译器的优化选项的4个级别,-O0表⽰没有优化,-O1为缺省值,-O3优化级别最⾼
-w 不⽣成任何警告信息。
-Wall ⽣成所有警告信息。
三 ⾃动化构建-make/Makefile
3.1 背景
会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒
⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作
makefile带来的好处就是⸺"⾃动化编译",⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。
make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可⻅,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。
make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。
3.2 基本使用
比如这么一个代码:
cpp
#include <stdio.h>
int main()
{
printf("hello Makefile!\n");
return 0;
}
我们编写一个Makefile文件:
cpp
myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean
clean:
rm -f myproc
依赖关系: 上⾯的⽂件myproc,它依赖myproc.c
依赖方法: gcc -o myproc myproc.c ,就是与之对应的依赖关系
项目清理:
⼯程是需要被清理的
像clean这种,没有被第⼀个⽬标⽂件直接或间接关联,那么它后⾯所定义的命令将不会被⾃动执⾏,不过,我们可以显⽰要make执⾏。即命令⸺"make clean",以此来清除所有的⽬标⽂件,以便重编译。
但是⼀般我们这种clean的⽬标⽂件,我们将它设置为伪⽬标,⽤ .PHONY 修饰,伪⽬标的特性
是,总是被执⾏的。
可以将我们的 hello ⽬标⽂件声明成伪⽬标,测试⼀下。
什么叫做总是被执⾏?
bash
$ stat XXX
File: 'XXX'
Size: 987 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1321125 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ whb) Gid: ( 1000/ whb)
Access: 2024-10-25 17:05:30.430619002 +0800
Modify: 2024-10-25 17:05:25.940595116 +0800
Change: 2024-10-25 17:05:25.940595116 +0800
⽂件 = 内容 + 属性
Modify: 内容变更,时间更新
Change:属性变更,时间更新
Access:常指的是⽂件最近⼀次被访问的时间。在Linux的早期版本中,每当⽂件被访问时,其atime
都会更新。但这种机制会导致⼤量的IO操作。具体更新原则,不做过多解释。
3.3 推导过程
bash
myproc:myproc.o
gcc myproc.o -o myproc
myproc.o:myproc.s
gcc -c myproc.s -o myproc.o
myproc.s:myproc.i
gcc -S myproc.i -o myproc.s
myproc.i:myproc.c
gcc -E myproc.c -o myproc.i
.PHONY:clean
clean:
rm -f *.i *.s *.o myproc
编译:
bash
$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc
make是如何⼯作的,在默认的⽅式下,也就是我们只输⼊make命令。那么:
make会在当前⽬录下找名字叫"Makefile"或"makefile"的⽂件。
如果找到,它会找⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会找到 myproc 这个⽂件,并把这个⽂件作为最终的⽬标⽂件。
如果 myproc ⽂件不存在,或是 myproc 所依赖的后⾯的 myproc.o ⽂件的⽂件修改时间要
⽐ myproc 这个⽂件新(可以⽤ touch 测试),那么,他就会执⾏后⾯所定义的命令来⽣成
myproc 这个⽂件。如果 myproc 所依赖的 myproc.o ⽂件不存在,那么 make 会在当前⽂件中找⽬标为
myproc.o ⽂件的依赖性,如果找到则再根据那⼀个规则⽣成 myproc.o ⽂件。(这有点像⼀
个堆栈的过程)当然,你的C⽂件和H⽂件是存在的啦,于是 make 会⽣成 myproc.o ⽂件,然后再⽤
myproc.o ⽂件声明 make 的终极任务,也就是执⾏⽂件 hello 了。这就是整个make的依赖性,make会⼀层⼜⼀层地去找⽂件的依赖关系,直到最终编译出第⼀个⽬标⽂件。
在找寻的过程中,如果出现错误,⽐如最后被依赖的⽂件找不到,那么make就会直接退出,并报错,⽽对于所定义的命令的错误,或是编译不成功,make根本不理。
make只管⽂件的依赖性,即,如果在我找了依赖关系之后,冒号后⾯的⽂件还是不在,那么对不起,我就不⼯作啦。
3.4 适度扩展语法
bash
BIN=proc.exe # 定义变量
CC=gcc
#SRC=$(shell ls *.c) # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c) # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名
OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表
LFLAGS=-o # 链接选项
FLAGS=-c # 编译选项
RM=rm -f # 引⼊命令
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^ # $@:代表⽬标⽂件名。 $^: 代表依赖⽂件列表
@echo "linking ... $^ to $@"
%.o:%.c # %.c 展开当前⽬录下所有的.c。 %.o: 同时展开同
名.o
@$(CC) $(FLAGS) $< # %<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。
@echo "compling ... $< to $@" # @:不回显命令
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN) # $(RM): 替换,⽤变量内容替换它
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)
总结:
本章节我们认识了编辑器vim,编译器gcc/g++,以及Makefile/make的相关用法,这样我们就基本了解了在linux下我们应该如何去写代码,编代码以及如何更高效的去做编译代码这件事,那么认识了这些,下一章节我们就要去认识linux下的debug是怎么样的。