系统编程之vim-gcc-动态库静态库-makefile-GDB调试
编辑器gedit介绍
gedit是一个GNOME桌面环境下兼容UTF-8的文本编辑器。它使用GTK+编写而成,因此它十分的简单易用,有良好的语法高亮,对中文支持很好,支持包括gb2312、gbk在内的多种字符编码。gedit是一个自由软件。
这是 Linux 下的一个纯文本编辑器,但你也可以把它用来当成是一个集成开发环境 (IDE), 它会根据不同的语言高亮显现关键字和标识符。
gedit是一个Linux环境下的文本编辑器,类似windows下的写字板程序,在不需要特别复杂的编程环境下,作为基本的文本编辑器比较合适。
简单理解就是一个文本编辑器,类似于nodepad++这种软件。
什么是vi(vim)
vi (Visual interface)编辑器是 Linux 系统中最常用的文本编辑器,vi 在Linux界有编辑器之神的美誉,几乎所有的 Linux 发行版中都包含 vi 程序。
vi 工作在字符模式下,不需要图形界面,非常适合远程及嵌入式工作,是效率很高的文本编辑器,尽管在 Linux 上也有很多图形界面的编辑器可用,但vi的功能是那些图形编辑器所无法比拟的。
vim 是 vi 的升级版,它不仅兼容 vi 的所有指令,而且还有一些新的特性,例如 vim 可以撤消无限次、支持关键词自动完成、可以用不同的颜色来高亮你的代码。vim 普遍被推崇为类 vi 编辑器中最好的一个。
vim工作模式
- 三种模式的切换图:
命令行模式
- 在shell编辑器中,输vim 文件名,进入该模式
- 在该模式下一切输入的命令都会当作命令来执行
编辑模式
- 在命令模式下输入i,a…等命令进入编辑模式
- 进入编辑模式就可以管理自己的文件内容
末行模式
- 在编辑模式下,按Esc键退出到命令行模式输入:,进入末行模式
- 之后按wq,q,q!等命令退回到shell界面
vim教程
vim基本操作
- vim 文件名,进入命令模式,之后按i,进入编辑模式,完成编辑后,按Esc键,退到末行模式,之后输入:,执行wq,q,q!等命令退回到shell界面
vim实用操作
命令行模式下的操作
- 常用代码
按键 | 功能 |
---|---|
gg=G | 格式化代码 |
- 切到编辑模式
按键 | 功能 |
---|---|
i | 光标位置当前处插入文字 |
I | 光标所在行首插入文字 |
o(字母) | 光标下一行插入文字(新行) |
O(字母) | 光标上一行插入文字(新行) |
a | 光标位置右边插入文字 |
A | 光标所在行尾插入文字 |
s | 删除光标后边的字符,从光标当前位置插入 |
S | 删除光标所在当前行,从行首插入 |
- 光标移动
按键 | 功能 |
---|---|
Ctrl+f | 向前滚动一个屏幕 |
Ctrl+b | 向后滚动一个屏幕 |
gg | 到文件第一行行首 |
G | 到文件最后一行行首 |
mG或mgg | 到指定行,m为目标行数 |
0(数字) | 光标移到到行首(第一个字符的位置) |
$ | 光标移到到行位 |
l(小写L) | 向右移动光标 |
h | 想左移动光标 |
k | 向上移动光标 |
j | 向下移动光标 |
^ | 光标移到到行首(第一个有效字符位置) |
- 复制粘贴
按键 | 功能 |
---|---|
[n]yy | 复制从当前行开始的n行 |
p | 把粘贴板上的内容插入到当前行 |
- 删除
按键 | 功能 |
---|---|
[n]x | 删除光标后n个字符 |
[n]X | 删除光标前n个字符 |
D | 删除光标所在开始到此行结尾的字符 |
[n]dd | 删除从当前行开始的n行(准确来说,是剪切,剪切不粘贴即为删除) |
dG | 删除光标所在开始到文件尾的所有字符 |
dw | 删除光标开始位置的字,包含光标所在字符 |
d0(0位数字) | 删除光标前本行所有内容,不包含光标所在字符 |
dgg | 删除光标所在开始到文件行首第一个字符开始的所有字符 |
- 撤销恢复
按键 | 功能 |
---|---|
.(点) | 执行上一次操作 |
u | 撤销前一个命令 |
Ctrl+r | 反撤销 |
100+. | 执行上一次操作100次 |
- 保存退出
按键 | 功能 |
---|---|
ZZ(shift+z+z) | 保存退出 |
- 查找
按键 | 功能 |
---|---|
/字符串 | 从当前光标位置向下查找(n,N查找内容切换) |
?/字符串 | 从当前光标位置向上查找(n,N查找内容切换) |
- 替换
按键 | 功能 |
---|---|
r | 替换当前字符 |
R | 替换当前行光标后的字符(按Esc退出替换模式) |
- 可视模式
按键 | 功能 |
---|---|
v | 按字符移动,选中文本,可配合h,j,k,l选择内容,使用d删除,使用y复制 |
shift+v | 行选(以行为单位)选中文本,可配合h,j,k,l选择内容,使用d删除,使用y复制 |
Ctrl+v | 列选 选中文本,可配合h,j,k,l选择内容,使用d删除,使用y复制 |
末行模式下的操作
- 保存退出
按键 | 功能 |
---|---|
:wq | 保存退出 |
:x | 保存退出 |
:w filename | 保存到指定文件 |
:q | 退出,如果文件修改但没有保存,会提示无法退出 |
:q! | 退出,不保存 |
all代表所有
举例:
wqall:表示全部保存退出
qall:表示全部不保存退出
- 替换
按键 | 功能 |
---|---|
: s/abc/123/ | 光标所在行的第一个abc替换为123 |
: s/abc/123/g | 光标所在行的所有abc替换为123 |
:1,10s/abc/123/g | 将第一行至第10行之间的abc全部替换成123 |
:%s/abc/123/g | 当前文件的所有abc替换为123 |
:%s/abc/123/gc | 同上,但是每次替换需要用户确认 |
:1,$s/abc/123/g | 当前文件的所有abc替换为123 |
- 分屏
按键 | 功能 |
---|---|
:sp | 当前文件水平分屏 |
:vsp | 当前文件垂直分屏 |
: sp 文件名 | 当前文件和另一个文件水平分屏 |
: vsp 文件名 | 当前文件和另一个文件垂直分屏 |
ctrl+w+w | 在多个窗口切换光标 |
:wall/:wqall/:qall | 保存/保存退出/退出所有分屏窗口 |
vim -O a.c b.c | 垂直分屏 |
vim -o a.c b.c | 水平分屏 |
- 其他用法扩展
按键 | 功能 |
---|---|
:!man 3 printf | 在vim中执行命令 (q退出) |
:r !ls -l | 将ls -l执行的结果写入当前文件中 |
:r /etc/passwd | 将/etc/passwd文件中的内容写入到当前文件中 |
:w /tmp/txt | 将当前文件内容写入到/tmp/txt文件中 |
:w! /tmp/txt | 强制将当前文件内容写入到/tmp/txt文件中 |
:1,10s/^g | 将第1行到10行行首添加// (^表示行首) //\转移字符 |
:1,10s#^#//#g | 将第1行到10行行首添加// (#可以临时代替/ 分隔) |
:%s/;/\r{\r\treturn0;\r}\r/g | 将;替换成{ return 0; } |
:1,10s#//##g | 将第1行到10行行首去掉// (#可以临时代替/ 分隔) |
- vim的配置
- 系统配置
- 配置文件:/etc/vim/vimrc
从网上搜索一个vim配置文件替换此文件,按自己的需求配置
- 会在每次启动vim工具时生效,作用于整个Linux系统,所有用户
- 内容:一系列的 末行命令
- 配置文件:/etc/vim/vimrc
- 用户配置
- 配置文件:~/.vimrc 该文件不存在,需要手动创建
- 会在每次启动vim时,自动生效,作用于当前用户
- 内容:一系列的 末行命令
- 配置文件:~/.vimrc 该文件不存在,需要手动创建
- 系统配置
GCC编译器简介
编辑器(如vi、记事本)是指我用它来写程序的(编辑代码),而我们写的代码语句,电脑是不懂的,我们需要把它转成电脑能懂的语句,编译器就是这样的转化工具。就是说,我们用编辑器编写程序,由编译器编译后才可以运行!
编译器是将易于编写、阅读和维护的高级计算机语言翻译为计算机能解读、运行的低级机器语言的程序。
gcc(GNU Compiler Collection,GNU 编译器套件),是由 GNU 开发的编程语言编译器。gcc原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,gcc同样适用于微软的Windows。
gcc最初用于编译C语言,随着项目的发展gcc已经成为了能够编译C、C++、Java、Ada、fortran、Object C、Object C++、Go语言的编译器大家族。
# 编译命令格式:
gcc [options] file...
g++ [options] file...
说明:
- 命令、选项和源文件之间使用空格分隔
- 一行命令中可以有零个、一个或多个选项
- 文件名可以包含文件的绝对路径,也可以使用相对路径
- 如果命令中不包含输出可执行文件的文件名,可执行文件的文件名会自动生成一个默认名,Linux平台为a.out,Windows平台为a.exe
GCC工作流程和常用选项
- gcc编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了4个步骤:
1.预处理:
- 将 源文件,展开 头文件、替换宏(变量宏、函数宏)、替换空行、空格、table
- gcc -E hello.c -o hello.i
* -E:预处理选项
* -o:重命名
2.编译:编译器
- 逐行检查程序中出现的 语法和词法错误!简单的逻辑错误。 -- 所有编译过程中,最耗时
- gcc -S hello.i -o hello.s
* -S:编译选项,如果编译无误,生成.s汇编文件
3.汇编:汇编器
- 将.s汇编文件中,所有汇编指令,翻译成二进制机器码
- gcc -c hello.s -o hello.o
* -c:汇编选项。无错误检查。机器翻译。
4.链接:连接器
- 将.o的目标文件,链接库文件、数据段合并、地址回填。生成可执行文件。
- gcc hello.c -o hello
* 此过程无专用参数。-o不是连接过程必须使用的参数。
- 4个步骤并不是gcc独立完成的,而是在内部调用其他工具,从而完成的整个工作流程:
gcc工作流程
# 创建并编辑hello.c文件
wjg@wjg:~$ vim hello.c
# 查看hello.c文件是否创建好
wjg@wjg:~$ ls hello.c
hello.c
# 第一步:进行预处理
wjg@wjg:~$ gcc -E hello.c -o hello.i
# 第二步:生成汇编文件
wjg@wjg:~$ gcc -S hello.i -o hello.s
# 第三步:生成目标代码
wjg@wjg:~$ gcc -c hello.s -o hello.o
# 第四步:生成可执行文件
wjg@wjg:~$ gcc hello.c -o hello
# 第五步:执行
wjg@wjg:~$ ./hello
hello,xiaowang
- 常用: 直接将源文件生成一个可以执行文件
wjg@wjg:~$ gcc hello.c -o hello1
wjg@wjg:~$ ./hello1
hello,xiaowang
- 如果不指定输出文件名字, gcc编译器会生成一个默认的可以执行a.out
wjg@wjg:~$ gcc hello.c
wjg@wjg:~$ ./a.out
hello,xiaowang
gcc常用选项
选项 | 作用 |
---|---|
-S(大写) | 只进行预处理和编译 |
-o file | 指定生成的输出文件名为file |
-E | 只进行预处理 |
-c(小写) | 只进行预处理、编译和汇编 |
-v / --version | 查看gcc版本号 |
-g | 包含调试信息 |
-On n=0~3 | 编译优化,n越大优化得越多 |
-Wall | 提示更多警告信息 |
-D | 编译时定义宏 |
- 显示所有警告信息
wjg@wjg:~$ gcc -Wall a.c
- 将警告信息当作错误处理
wjg@wjg:~$ gcc -Wall -Werror a.c
- 测试程序(-D选项):test.c
#include <stdio.h>
int main(void)
{
printf("SIZE:%d\n",SIZE);
return 0;
}
wjg@wjg:~$ gcc test.c -DSIZE=10
wjg@wjg:~$ ./a.out
SIZE:10
静态库和动态库
函数库
- 本质:一组函数。具有相近的功能或操作同一数据结构。
- <string.h>:strcpy/strcmp/strcat/strlen/strstr/strchr/strtok…
- 自定义库:<mysort.h>:bubble_sort/select_sort/qucik_sort/insert_sort…
- 作用:
- 代码复用
- 程序积累
- 发布形式:
- 源码形式:
- 优点:方便使用者学习和使用。
- 缺点:1.保密性差。2.编译程序耗时。3.编译受平台、版本限制。
- 二进制形式:
- 优点、缺点,与上述相反
- 源码形式:
- 我们使用的函数库:标准C库:/lib/x86_64-linux-gnu/libc.so.6
静态库
简述
- 机制: 在编译程序时,复制静态库的代码片,到可执行程序中。
- 优点:将函数库中的函数本地化。寻址方便,速度快。(库函数执行效率==自定义函数执行效率)
- 缺点:消耗系统资源大,每个使用静态库的程序,都要复制一份,静态库。浪费内存。
- 使用场景:多用于核心程序,保证时效性,可以忽略空间。
制作
- 生成*.o目标文件。
gcc add.c sub.c mul.c -c
#得到add.o sub.o mul.o
- 制作静态库
ar rcs lib静态库名.a add.o sub.o mul.o
# ar:制作静态库的工具。gcc不具备制作静态库的功能
# r:更新。c:创建(可省)。s:建立索引。
# 静态库名:必须lib开头,以.a为后缀名。 - 使用file lib静态库名.a查看
- 使用静态库
gcc ./src/hello.c -o app -L ./lib -l mymath -I ./inc
# -L:表示要连接的库所在目录
# -I./: I(大写i) 表示指定头文件的目录为当前目录
#-l(小写L):指定链接时需要的库,去掉前缀和后缀
- 查看静态库
file 文件名
file libmymath.a
# 查看信息如下:
libmymath.a: current ar archive
动态库(共享库)
简介
- 机制:代码共享
- 优点:节省内存(共享)、易于更新(动态链接)
- 缺点:相对于静态库而言,函数调用速度慢(函数地址"延时绑定")
- 使用场景:
- 对程序执行的速度要求不是强烈,而对系统资源有一定要求的场景。
- 对于更新比较频繁程序。
- 停止运行程序
- 使用新库覆盖旧库(保证新库、旧库名称一致。接口一致。)
- 重启程序
重点强调
- 动态库是否加载到内存,取决于"程序是否运行"。
- 动态库加载至内存位置,不固定。
制作
- 生成与位置无关的 目标文件(*.o文件)
gcc -fPCI -c add.c sub.c mul.c
- 制作动态库
gcc -shared -o lib动态库名字.so add.o sub.o mlu.o
- 测试用静态库
gcc hello.c -o app1 -L ./lib -l mymath -I ./inc
- 查看静态库
file 文件名
# 例如
file libmymath.so
# 查看信息如下:
libmymath.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=41bf6a05764c5811c69e232d3e68ad17068b5b18, not stripped
- 启动 程序./app—>报错:
# 报错信息如下:
./app1: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
- 错误原因:"动态链接器"ld-linux-x86-64.so.2搜索动态库的路径没有指定
- 链接器:工作在gcc编译4过程中的"链接阶段"。工作结束,生成可执行文件
- 动态链接器:工作于可执行程序运行之后,辅助加载器负责将动态库加载到内存
- 查看错误:
6. 解决上述错误。–基本思想:给 动态链接器指定动态库路径
- 环境变量法:
# 将当前动态库所在目录,加入到环境变量中
# 失效时间:终端一旦退出,环境变量的修改无效
export LD_LIBRARY_PATH:./lib
- 配置文件法:将上述修改的环境变量的指令,写入到.bashrc中,每次启动的时候都会自动生效
# 打开bashrc文件
vim bashrc
# 配置如下命令:
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:./lib
# 重新启动.bashrc文件
. .bashrc 或者 source .bashrc
- 常用:缓存文件法,通过修改配置文件,修改缓存文件,生成动态库链接器需要搜寻的目录位置。
# 打开配置文件
sudo vim /etc/ld.so.conf
# 修改配置文件:将 动态库 的绝对路径添加到/etc/ld.so.conf文件中
# 使用以下命令,动态更新ld.so.cache,该文件直接影响动态链接器搜索动态库位置。
sudo ldconfig -v
makefile
- 作用:进程项目管理。
- 初步学习:1个规则、2个函数、3个自动变量
- 语法
- 要想使用默认的make命令,管理项目。makefile文件名:必须是"makefile"或者"Makefile"
目标:依赖条件
(一个tab缩进)命令
# 举例
hello:hello.c
gcc hello.c -o hello
- 目标时间,必须晚于依赖条件的时间,否则,更新目标
- 依赖条件,如果不存在,寻找新的规则去产生依赖条件
hello:hello.o add.o sub.o mul.o
gcc hello.c add.c sub.c mul.c -o hello
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
mul.o:mul.c
gcc -c mul.c -o mul.o
2个函数
wildcard函数:用来匹配
src = $(wildcard ./*c) :匹配当前工作目录下的所有.c文件。将文件名组成列表,赋值给变量src
相当于:src=add.c sub.c mul.c
patsubst函数:用来替换
obj = $(patsubst %.c,%.o,$(src)) :将参3中,包含参1部分,替换为参2
相当于obj=add.o sub.o mul.o
obj = $(patsubst %.c,%,$(src))
相当于obj=add sub mul
3个自动变量
- 普通变量(自定义变量)
定义变量语法:变量名=变量值(都是字符串)
举例:
foo = abc
取变量值:$(变量)
举例:
bar = $(foo)===>bar=abc
- 自动变量
$@:在规则命令中,表示规则中的目标
$^:在规则命令中,表示所有依赖条件
$<:在规则命令中,表示第一个依赖条件。如果将该变量应用在"模式规则"中,它可以将依赖条件列表中的每一个依赖,依次取出,套用模式规则
其他关键字
ALL:
用来给makefile文件,指定"终极目标"
makefile文件,默认规则为:从上到下,碰到第一 规则中的目标,为"终极目标"。我们可以使用ALL指定终极目标。
clean:
用来借助makefile清除项目中的指定文件。如:*.o、a.out
举例:
clean:
-rm -rf $(obj) a.out
模式规则
- 可以将makefile文件中,具有严格格式的规则,使用模式规则代替。要求模式规则中,只能使用"$<"符号。
%.o:%.c
gcc -c $< -o $@
- 静态模式规则
$(obj):%.o:%.c
gcc -c $< -o $@
伪目标
- 针对,残缺的规则,也能使之生成目标。
.PHONY:clean ALL
其他参数
-n:模拟指定makefile,不真正执行!推荐首次编写makefile完成时,使用。
-f:指定命名为非"makefile"的文件。执行make命令
完成版的makefile
src = $(wildcard *.c)
obj = $(patsubst %.c,%.o,$(src))
CC = gcc
target = hello
ALL:$(target)
$(target):$(obj)
$(CC) $^ -o $@
$(obj):%.o:%.c
$(CC) -c $< -o $@
clean:
-rm -rf $(obj) $(target)
.PHONY:clean ALL
GDB调试器
要求
- 程序必须是自己编写的(能完全看懂),这样调试才有意义
- 只能用来调试逻辑错误!
- 必须添加-g参数,使用gcc编译生成的可以执行的文件,才能调试!
基础指令
命令 | 解释 |
---|---|
-g | 必须使用该参数编译可执行文件,否则没有调试表 |
gdb | ./a.out |
list 1 | 列出源码,根据源码指定行号设置断点。1代表从第一行开始 |
b 55 | 在第55行添加断点 |
run/r | 运行程序,启动调试! 代码会自动运行,停止在断点处。断点对应的代码行,没有执行! |
n/next | 下一条指令(越过函数,不进入函数) |
s/step | 下一条执行(进入函数) |
p/print | 打印变量值。如:p var。查看var变量的值。 |
continue | 继续执行断点后续的指令 |
finish | 结束当前函数调用 |
quit | 退出当前gdb调试 |
info b | 查看断点详细信息 |
其他指令
命令 | 解释 |
---|---|
start | 不使用断点,直接启动程序,开始单步调试 |
run | 找出程序出现段错误的位置。用法:gdb启动调试,直接run。停止的位置,就是代码出错的代码位置 |
set args 参1 参2 参3 | 设置main函数命令行参数,必须在start/run之前设置 |
run 参1 参2 参3 | 设置main函数命令行参数 |
b 23 if = 5 | 设置条件断点。只能满足该条件时,断点才生效 |
disable 2 | 设置编号为2号的断点,失效。使用info b查看 |
enable 3 | 设置编号为3号的断点,生效。使用info b查看 |
delete 1 | 删除编号为1号的断点 |
ptype | 查看变量类型 |
display | 设置跟踪变量。如display i。跟踪i的变量 |
undisplay | 取消跟踪变量。使用跟踪变量的编号。如:undisplay 2 。取消2号的变量跟踪 |
bt | 列出当前程序,正存活着的栈帧 |
frame | 根据栈帧编号,切换栈帧,例如:frame 1,切换到编号为1的栈帧 |
后记
学习很苦,但是为了生活不得不努力疯狂式学习。生活不会那么轻松,在大学,学的知识只是带我们入门,等毕业了才发现在大学学的东西不足以让我们找到合适的工作,只能用最短的时间学这些知识。此笔记整理,是为了让自己更好的理解,同时也希望此篇笔记能帮到有需要的人。