vim - gcc - 库的制作和使用
学习目标:
- 掌握 vim 命令模式下相关命令的使用
- 掌握从命令模式切换到编辑模式的相关命令
- 掌握 vim 末行模式下相关命令的使用
- 能够说出 gcc 的工作流程和掌握常见参数的使用
- 熟练掌握 Linux 下的静态库的制作和使用
- 熟练掌握 Linux 下的共享库的制作和使用
vim
vim 的简单介绍
vi 是”visual interface”的简称,它在 Linux 上的地位就仿佛 Windows 中的记事本一样。它可以执行编辑、删除、查找、替换、块操作等众多文本操作,而且用户可以根据自己的需要对其进行定制。vi 是一个文本编辑程序,没有菜单,只有命令。
vim 更高级一些,可以理解是 vi 的高级版本。
vim 需要自行安装,在 shell 中输入 vimtutor
命令可以查看相关的帮助文档。安装命令如下:
$ sudo apt install vim
vim 的三种模式
vi 有三种基本工作模式:命令模式、文本输入模式、末行模式。三种工作模式的切换如下图所示,从图中可以看出编辑模式和末行模式之间不能相互切换,必须经过命令模式。
vim 的基本操作
命令模式下的操作
当使用 vim 打开一个新文件开始也是进入命令模式下,或者在编辑模式下按下 ESC
键也可以进入命令模式。
- 保存退出:
shift + zz
- 光标移动:
h/j/k/l
光标左/下/上/右移动,w/b
:向前/向后移动一个单词gg
:光标移动到文件开头G
:光标移动到文件末尾0
:光标移动到行首$
:光标移动到行尾n + G
:光标移动到跳转到指定的行
- 删除命令:
x
:删除光标后一个字符,相当于Del
X
:删除光标前一个字符,相当于Backspace
dw
:删除光标开始位置的字符,包含光标所在字符d0
:删除光标前本行所有内容,不包含光标所在字符D/d$
:删除光标后本行所有内容,包含光标所在字符dd
:删除光标所在行(本质其实是剪切)ndd
:从光标当前行向下删除指定的行数,如15dd
v/ctrl+v
:使用h
、j
、k
、l
移动选择内容,然后按d
删除其中ctrl+v
是列模式,v
为非列模式
- 复制粘贴:
yy
:复制当前行nyy
:复制 n 行,如10yy
p
:在光标所在位置向下新开辟一行,粘贴P
:在光标所在位置向上新开辟一行,粘贴- 剪切操作:按
dd
或者ndd
删除,将删除的行保存到剪贴板中,然后按p/P
就可以粘贴了
- 撤销和反撤销:
u
:一步一步撤销,相当于 word 文档的ctrl+z
Ctrl+ r
:反撤销,相当于 word 文档的ctrl+y
- 可视模式:
v/ctrl+v
:使用h
、j
、k
、l
移动选择内容;- 使用
d
删除 - 使用
y
复制 - 使用
p
粘贴到光标的后面 - 使用
P
粘贴到光标的前面
- 使用
- 替换操作:
r
:替换当前字符R
:替换当前行光标后的字符
- 查找命令:
/
:/xxxx
,从光标所在的位置开始搜索,按 n 向下搜索,按 N 向上搜索?
:?xxxx
,从光标所在的位置开始搜索,按 n 向上搜索,按 N 向下搜索#
:将光标移动到待搜索的字符串上,然后按 n 向上搜索,但 N 向下搜索shift+k
:在待搜索的字符串上按shift+k
或者K
,可以查看相关的帮助文档
文本输入模式下的操作
从命令模式切换到文本输入模式的命令如下:
i
:在光标前插入a
:在光标后插入I
:在光标所在行的行首插入A
:在光标所在行的行尾插入o
:在光标所在的行的下面新创建一行, 行首插入O
:在光标所在的行的上面新创建一行, 行首插入s
:删除光标后边的字符, 从光标当前位置插入S
:删除光标所在当前行, 从行首插入- 按列模式插入:先按
ctrl+v
进入列模式,按hjkl
移动选定某列,按I
或者shift+i
向前插入,然后插入字符,最后按两次esc
末行模式下的操作
从命令模式切换到末行模式,输入 :
- 保存退出:
q
:退出q!
:强制退出,不保存修改内容w
:保存修改内容,不退出wq
:保存并退出x
:相当于 wq
- 替换操作:
:s/old/new/
:光标所在行的第一个 old 替换为 new:s/old/new/g
:光标所在行的所有 old 替换为 new:m, ns/old/new/g
:将第 m 行至第 n 行之间的 old 全部替换成 new:%s/old/new/g
:当前文件的所有 old 替换为 new:1,$s/old/new/g
:当前文件的所有 old 替换为 new:%s/old/new/gc
:同上,但是每次替换需要用户确认
- 快速翻屏:
ctrl + u
:向下翻半屏(up)–光标向上移动ctrl + d
:向上翻半屏(down)–光标向下移动ctrl + f
:向上翻一屏(front)ctrl + b
:向后翻一屏(back)
- 分屏操作:
sp
:当前文件水平分屏vsp
:当前文件垂直分屏sp 文件名
:当前文件和另一个文件水平分屏vsp 文件名
:当前文件和另一个文件垂直分屏ctrl-w-w
:在多个窗口切换光标wall/wqall/xall/qall/qall!
:保存/保存退出/保存退出/退出/强制退出分屏窗口
vim 的配置文件
我们可以看到别人的 vim 非常的优美,这都是由用户自己配置的,推荐在 ~/.vimrc
文件进行修改,此文件是用户级别的配置,而不是使用 /etc/vim/vimrc
文件进行系统级别的配置
配置示例:
set tabstop=4 " 设置缩进4个空格
set nu " 设置行号
set shiftwidth=4 " 设置 gg=G 缩进4个空格, 默认是缩进8个空格
由于 Linux 是多用户操作系统,建议只在用户级别的配置文件下进行修改,不要影响其他用户。
gcc 编译器
gcc 工作流程
gcc 编译器将 c 源文件编译成一个可执行程序,中间一共经历四个步骤:
四个步骤并不是 gcc 独立完成的,而是在内部调用了其他工具,从而完成了这个工作流程,其中编译过程是最耗时的,因为要逐行检查语法。
以 test.c
为例介绍 gcc 的四个步骤:
- 预处理:
gcc -E test.c -o test.i
- 编译:
gcc -S test.i -o test.s
- 汇编:
gcc -c test.s -o test.o
- 链接:
gcc test.o -o test
一步生成最终的可执行程序:gcc test.c -o test
gcc 常用参数
gcc 的常用参数有以下几点:
-v
:查看 gcc 版本号,--version
也可以-E
:生成预处理文件-S
:生成汇编文件-c
:只编译,生成.o
文件,通常称为目标文件-I
:指定头文件所在的路径-L
:指定库文件所在的路径-l
:指定库的名字-o
:指定生成的目标文件的名字-g
:包含调试信息,使用 gdb 调试需要添加-g
参数-On
:n=0∼3
编译优化,n 越大优化得越多,如下的代码片段
int a = 10;
int b = a;
int c = b;
printf("%d", c);
上面的代码可能会被编译器优化成:
int c = 10;
printf("%d", 10);
-Wall
:提示更多警告信息,如下代码片段
int a;
int b;
int c = 10;
printf("[%d]\n", c);
编译如下:
$ gcc -o test -Wall test.c
warning: unused variable ‘b’ [-Wunused-variable]
warning: unused variable ‘a’ [-Wunused-variable]
-D
:编译时定义宏,如在test.c
文件中的代码片段
printf("MAX = [%d]\n", MAX);
编译:
gcc -o test test.c -D MAX=10
gcc -o test test.c -DMAX=10
静态库和共享库
库的介绍
库是二进制文件,是源代码文件的另一种表现形式。是加了密的源代码,是一些功能相近或相似的函数的集合体。库能提高代码的可重用性,可以提高程序的健壮性,可以减少开发者的代码开发量,缩短开发周期。
库一般包含两种文件,头文件和库文件:
- 头文件包含库函数的声明
- 库文件包含库函数的代码实现
注意:库不能单独使用,只能作为其他执行程序的一部分完成某些功能,也就是说只能被其他程序调用才能使用。
库可以分为静态库和共享库
静态库
静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。按照习惯,一般以 .a
做为文件后缀名。静态库的命名一般分为三个部分:
- 前缀为
lib
- 库名称是自定义的,追加在
lib
后,如test
- 后缀为
.a
所以静态库的整体文件名一般为 libXXX.a
,如 libtest.a
。
既然了解了静态库的文件名,那么静态库是如何生成的呢?下面以 func1.c
、func2.c
和 head.h
三个文件为例讲述静态库的制作和使用,其中 head.h
的文件中有函数的声明,func1.c
和 func2.c
有函数的实现。
- 将源文件生成对应的目标文件
$ gcc -c func1.c -o func1.o
$ gcc -c func2.c -o func2.o
- 使用打包工具
ar
将准备好的目标文件打包成库,打包时会使用到参数r
更新、c
创建、s
建立索引
$ ar rcs libtest1.a func1.o func2.o
具体流程如下所示:
静态库的使用:静态库制作完成之后,需要将库文件和头文件一起发布给用户。假设测试文件为 main.c
,静态库文件为libtest1.a
,头文件为 head.h
。用到的参数:
-L
:指令要链接的库的所在目录-l
:指定链接时需要的静态库,去掉前缀和后缀-I
:指定main.c
文件用到的头文件head.h
所在的路径
使用命令如下:
$ gcc main.c -L./ -I./ -o main1 -ltest1
静态库的优缺点:
- 优点:
- 函数库最终被打包到应用程序中,实现是函数本地化,寻址方便、速度快(库函数调用效率==自定义函数使用效率)
- 程序在运行时与函数库再无瓜葛,移植方便
- 缺点:
- 消耗系统资源较大,每个进程使用静态库都要复制一份,无端浪费内存
- 静态库会给程序的更新、部署和发布带来麻烦。如果静态库
libxxx.a
更新了,所有使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载)
共享库
共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的拷贝,规避了空间浪费问题。共享库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布会带来麻烦。用户只需要更新共享库即可,增量更新。为什么需要共享库,其实也是静态库的特点导致。按照习惯,一般以 .so
做为文件后缀名。共享库的命名一般分为三个部分:
- 前缀为
lib
- 库名称可以自己定义,如
test
- 后缀为
.so
所以共享库的整体文件名一般为 libXXX.so
,如 libtest.so
。
共享库的制作与静态库不同,共享库的制作还是需要依赖于 gcc,具体流程如下:
- 将源文件生成目标文件,此时要加上编译选项
-fPIC
或-fpic
,表示创建于地址无关的编译程序,目的就是为了能够在多个应用程序间共享
$ gcc -fPIC -c func1.c -o func1.o
$ gcc -fPIC -c func2.c -o func2.o
- 生成共享库,此时需要加上链接器选项
-shared
,指定生成动态链接库
$ gcc -shared func1.o func2.o -o libtest2.so
共享库的使用:共享库的使用方式与静态库一样。用到的参数:
-L
:指令要链接的库的所在目录-l
:指定链接时需要的静态库,去掉前缀和后缀-I
:指定main.c
文件用到的头文件head.h
所在的路径
使用命令如下,运行后发现出现了错误:
$ gcc main.c -L./ -I./ -o main2 -ltest2
$ ./main2
为什么在执行的时候找不到
libtest2.so
库?
- 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道所依赖的库的绝对路径。此时就需要系统动态载入器(dynamic linker/loader)
ldd
命令可以查看可执行文件依赖的库文件,执行ldd main2
,可以发现libtest2.so
找不到
- 对于 elf 格式的可执行程序,是由
ld-linux.so*
来完成的,它搜索步骤为 elf 文件的DT_RPATH
段 —> 环境变量LD_LIBRARY_PATH
—>/etc/ld.so.cache
文件列表 —>/lib/
或/usr/lib
目录找到库文件后将其载入内存。
如何让系统找到共享库:
- 拷贝自己制作的共享库到
/lib
或者/usr/lib
- 临时设置:
LD_LIBRARY_PATH
:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
- 永久设置:把
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
设置到∼/.bashrc
文件中,然后在执行下列三种办法之一:- 执行
. ~/.bashrc
使配置文件生效(第一个.
后面有一个空格) - 执行
source ~/.bashrc
配置文件生效 - 退出当前终端,然后再次登陆也可以使配置文件生效
- 执行
- 永久设置:把
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
设置到/etc/profile
文件中(不推荐) - 将其添加到
/etc/ld.so.cache
文件中- 编辑
/etc/ld.so.conf
文件,加入库文件所在目录的路径 - 运行
sudo ldconfig -v
,该命令会重建/etc/ld.so.cache
文件
- 编辑
解决了库的路径问题之后,再次 ldd
命令可以查看可执行文件依赖的库文件 ldd main2
共享库的特点:
- 动态库把对一些库函数的链接载入推迟到程序运行的时期
- 可以实现进程之间的资源共享(因此动态库也称为共享库)
- 将一些程序升级变得简单
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)
比较静态库和动态库的优缺点
静态库的优点:
- 执行速度快,是因为静态库已经编译到可执行文件内部了
- 移植方便,不依赖于其他的库文件
静态库的缺点:
- 耗费内存,是由于每一个静态库的可执行程序都会加载一次
- 部署更新麻烦,因为静态库修改以后所有的调用到这个静态库的可执行文件都需要重新编译
动态库的优点:
- 节省内存
- 部署升级更新方便,只需替换动态库即可,然后再重启服务
动态库的缺点:
- 加载速度比静态库慢
- 移植性差, 需要把所有用到的动态库都移植
由于由静态库生成的可执行文件是把静态库加载到了其内部,所以静态库生成的可执行文件一般会比动态库大。
推荐阅读 C/C++ 静态库与动态库进一步了解。
练习
编写 4 个 .c
文件:加、 减、乘、除,分别使用动态库和静态库。