认识linux -- 编辑器vim以及编译器gcc/g++和Makefile文件
一. 认识编辑器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 letTlist_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|TagListnmap wm :WMToggle<cr>然后重启vim,打开~/XXX.c或〜/XXX.cpp, 在normal状态下输⼊"wm", 你将看到上图的效果。
二 编译器GCC/G++
2.1 背景知识
1. 预处理(进⾏宏替换/去注释/条件编译/头⽂件展开等)2. 编译(⽣成汇编)3. 汇编(⽣成机器可识别代码)4. 连接(⽣成可执⾏⽂件或库⽂件)
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动态链接库:
$ 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 hellogcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证。
⼀般我们的云服务器,C/C++的静态库并没有安装,可以采⽤如下⽅法安装:
# Centos
yum install glibc-static libstdc++-static -y# ubuntu
apt install glibc-static libstdc++-static -y2.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 基本使用
比如这么一个代码:
#include <stdio.h>
int main()
{
printf("hello Makefile!\n");
return 0;
}我们编写一个Makefile文件:
myproc:myproc.cgcc -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 ⽬标⽂件声明成伪⽬标,测试⼀下。
什么叫做总是被执⾏?
$ 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 推导过程
myproc:myproc.ogcc myproc.o -o myproc
myproc.o:myproc.sgcc -c myproc.s -o myproc.o
myproc.s:myproc.igcc -S myproc.i -o myproc.s
myproc.i:myproc.cgcc -E myproc.c -o myproc.i.PHONY:clean
clean:rm -f *.i *.s *.o myproc编译:
$ 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 myprocmake是如何⼯作的,在默认的⽅式下,也就是我们只输⼊make命令。那么:
1. make会在当前⽬录下找名字叫“Makefile”或“makefile”的⽂件。2. 如果找到,它会找⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会找到 myproc 这个⽂件,并把这个⽂件作为最终的⽬标⽂件。3. 如果 myproc ⽂件不存在,或是 myproc 所依赖的后⾯的 myproc.o ⽂件的⽂件修改时间要⽐ myproc 这个⽂件新(可以⽤ touch 测试),那么,他就会执⾏后⾯所定义的命令来⽣成myproc 这个⽂件。4. 如果 myproc 所依赖的 myproc.o ⽂件不存在,那么 make 会在当前⽂件中找⽬标为myproc.o ⽂件的依赖性,如果找到则再根据那⼀个规则⽣成 myproc.o ⽂件。(这有点像⼀个堆栈的过程)5. 当然,你的C⽂件和H⽂件是存在的啦,于是 make 会⽣成 myproc.o ⽂件,然后再⽤myproc.o ⽂件声明 make 的终极任务,也就是执⾏⽂件 hello 了。6. 这就是整个make的依赖性,make会⼀层⼜⼀层地去找⽂件的依赖关系,直到最终编译出第⼀个⽬标⽂件。7. 在找寻的过程中,如果出现错误,⽐如最后被依赖的⽂件找不到,那么make就会直接退出,并报错,⽽对于所定义的命令的错误,或是编译不成功,make根本不理。8. make只管⽂件的依赖性,即,如果在我找了依赖关系之后,冒号后⾯的⽂件还是不在,那么对不起,我就不⼯作啦。
3.4 适度扩展语法
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是怎么样的。
