vim-gcc-库的制作和使用

vim - gcc - 库的制作和使用

学习目标

  1. 掌握 vim 命令模式下相关命令的使用
  2. 掌握从命令模式切换到编辑模式的相关命令
  3. 掌握 vim 末行模式下相关命令的使用
  4. 能够说出 gcc 的工作流程和掌握常见参数的使用
  5. 熟练掌握 Linux 下的静态库的制作和使用
  6. 熟练掌握 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:使用 hjkl 移动选择内容,然后按 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:使用 hjkl 移动选择内容;
      • 使用 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 参数
  • -Onn=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.cfunc2.chead.h 三个文件为例讲述静态库的制作和使用,其中 head.h 的文件中有函数的声明,func1.cfunc2.c 有函数的实现。

  1. 将源文件生成对应的目标文件
$ gcc -c func1.c -o func1.o
$ gcc -c func2.c -o func2.o
  1. 使用打包工具 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,具体流程如下:

  1. 将源文件生成目标文件,此时要加上编译选项 -fPIC-fpic,表示创建于地址无关的编译程序,目的就是为了能够在多个应用程序间共享
$ gcc -fPIC -c func1.c -o func1.o
$ gcc -fPIC -c func2.c -o func2.o
  1. 生成共享库,此时需要加上链接器选项 -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_PATHexport 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

在这里插入图片描述

共享库的特点

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期
  • 可以实现进程之间的资源共享(因此动态库也称为共享库)
  • 将一些程序升级变得简单
  • 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)

比较静态库和动态库的优缺点

静态库的优点:

  1. 执行速度快,是因为静态库已经编译到可执行文件内部了
  2. 移植方便,不依赖于其他的库文件

静态库的缺点:

  1. 耗费内存,是由于每一个静态库的可执行程序都会加载一次
  2. 部署更新麻烦,因为静态库修改以后所有的调用到这个静态库的可执行文件都需要重新编译

动态库的优点:

  1. 节省内存
  2. 部署升级更新方便,只需替换动态库即可,然后再重启服务

动态库的缺点:

  1. 加载速度比静态库慢
  2. 移植性差, 需要把所有用到的动态库都移植

由于由静态库生成的可执行文件是把静态库加载到了其内部,所以静态库生成的可执行文件一般会比动态库大。

推荐阅读 C/C++ 静态库与动态库进一步了解。

练习

编写 4 个 .c 文件:加、 减、乘、除,分别使用动态库和静态库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xjjeffery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值