linux系统编程2(vim,gcc,静态库,动态库,GDB)

这篇博客详细介绍了Linux系统编程中的vim编辑器,包括其工作模式、教程和基本操作。接着讲解了GCC编译器的工作流程和常用选项。在库的使用方面,对比了静态连接和动态连接的优缺点,详细阐述了动态库的制作与使用。最后,文章详细探讨了GDB调试器的使用,包括启动、设置调试信息、断点操作和数据查看等关键功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux系统编程2(vim,gcc,静态库,动态库,GDB)

1vim编辑器

vim 是 vi 的升级版,它不仅兼容 vi 的所有指令,而且还有一些新的特性,例如 vim 可以撤消无限次、支持关键词自动完成、可以用不同的颜色来高亮你的代码。vim 普遍被推崇为类 vi 编辑器中最好的一个。

1.1 vim工作模式

vi有三种基本工作模式: 命令模式、文本输入模式(编辑模式)、末行模式。
vim工作模式
1.1.1 命令模式

任何时候,不管用户处于何种模式,只要按一下ESC键,即可使vi进入命令模式。我们在shell环境(提示符为$)下输入启动vim命令,进入编辑器时,也是处于该模式下。

在命令模式下,用户可以输入各种合法的vi命令,用于管理自己的文档。此时从键盘上输入的任何字符都被当做编辑命令来解释,若输入的字符是合法的vi命令,则vi在接受用户命令之后完成相应的动作。但需注意的是,所输入的命令并不在屏幕上显示出来。若输入的字符不是vi的合法命令,vi会响铃报警。

1.1.2 编辑模式

在命令模式下输入插入命令i(I)、附加命令a(A) 、打开命令o(O)、替换命s(S)都可以进入文本输入模式,此时vi窗口的最后一行会显示“插入”。

在该模式下,用户输入的任何字符都被vi当做文件内容保存起来,并将其显示在屏幕上。在文本输入过程中,若想回到命令模式下,按键ESC即可。

1.1.3 末行模式

末行模式下,用户可以对文件进行一些附加处理。尽管命令模式下的命令可以完成很多功能,但要执行一些如字符串查找、替换、显示行号等操作还是必须要进入末行模式的。

在命令模式下,输入冒号即可进入末行模式。此时vi窗口的状态行会显示出冒号,等待用户输入命令。用户输入完成后,按回车执行,之后vi编辑器又自动返回到命令模式下。

1.2 vim教程

[wiggins@localhost blog]$ vimtutor

vim教程

1.3 vim基本操作

1.3.1 命令模式下的操作

  • =>1)切换到编辑模式
按键功能
i光标位置当前处插入文字
I(大写i)光标所在行首插入文字
o光标下一行插入文字(新行)
O(大写o)光标上一行插入文字(新行)
a光标位置右边插入文字
A光标所在行尾插入文字
s删除光标后边的字符
S删除光标所在当前行,从行首插入
  • 2)光标移动
按键功能
Ctrl + f向前滚动一个屏幕
Ctrl + b向后滚动一个屏幕
gg到文件第一行行首
G(大写)到文件最后一行行首,G必须为大写
mG或mgg到指定行,m为目标行数
0(数字)光标移到到行首(第一个字符位置)
$光标移到到行尾
l(小写L)向右移动光标
h向左移动光标
k向上移动光标
j向下移动光标
^光标移到到行首(第一个有效字符位置)
  • 3)复制粘贴
按键功能
[n]yy复制从当前行开始的 n 行
p把粘贴板上的内容插入到当前行
  • 4)删除
按键功能
[n]x删除光标后 n 个字符
[n]X删除光标前 n 个字符
D删除光标所在开始到此行尾的字符
[n]dd删除从当前行开始的 n 行(准确来讲,是剪切,剪切不粘贴即为删除)
dG删除光标所在开始到文件尾的所有字符
dw删除光标开始位置的字,包含光标所在字符
d0(0为数字)删除光标前本行所有内容,不包含光标所在字符
dgg删除光标所在开始到文件首行第一个字符开始的所有字符
  • 5)撤销恢复
按键功能
.(点)执行上一次操作
u撤销前一个命令
ctrl+r反撤销
100+.执行上一次操作100次
  • 6)保存退出
按键功能
ZZ(shift+z+z)保存退出
  • 7)查找
按键功能
/字符串从当前光标位置向下查找(n,N查找内容切换)
?字符串从当前光标位置向上查找(n,N查找内容切换)
  • 8)替换
按键功能
r替换当前字符
R替换当前行光标后的字符(ESC退出替换模式)
  • 9)可视模式
按键功能
v按字符移动,选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制
Shift + v行选(以行为单位)选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制
Ctrl + v列选 选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制

1.3.2 末行模式下的操作

  • 1)保存退出
按键功能
:wq保存退出
:x(小写)保存退出
:w filename保存到指定文件
:q退出,如果文件修改但没有保存,会提示无法退出
:q!退出,不保存

all 表示所有

  • 2)替换
按键功能
: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
  • 3)分屏
按键功能
:sp当前文件水平分屏
:vsp当前文件垂直分屏
: sp 文件名当前文件和另一个文件水平分屏
: vsp 文件名当前文件和另一个文件垂直分屏
ctrl-w-w在多个窗口切换光标
:wall/:wqall/:qall保存/保存退出/退出所有分屏窗口
vim -O a.c b.c垂直分屏
vim -o a.c b.c水平分屏
    1. 其它用法(扩展)
按键功能
:!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行行首去掉// (#可以临时代替/ 分隔)
    1. 配置文件

局部配置文件(推荐)

[wiggins@localhost blog]$ vim ~/.vimrc

全局配置文件:

[wiggins@localhost blog]$ sudo vim /etc/vim/vimrc

2 GCC编译器

编译器是将易于编写、阅读和维护的高级计算机语言翻译为计算机能解读、运行的低级机器语言的程序。

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

2.1 GCC工作流程和常用选项

gcc编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了四个步骤:
文件编译过程
四个步骤并不是gcc独立完成的,而是在内部调用了其他工具,从而完成了整个工作流程:
文件编译展示
gcc工作的流程

[wiggins@localhost blog]$ ls 1hello.c
  • 第一步: 进行预处理
[wiggins@localhost blog]$ gcc -E 1hello.c -o 1hello.i
  • 第二步: 生成汇编文件
[wiggins@localhost blog]$ gcc -S 1hello.i -o 1hello.s
  • 第三步: 生成目标代码
[wiggins@localhost blog]$ gcc -c 1hello.s -o 1hello.o
  • 第四步: 生成可以执行文件
[wiggins@localhost blog]$ gcc 1hello.o -o 1hello 

第五步: 执行

[wiggins@localhost blog]$./1hello hello itcast

gcc常用选项

选项作用
-o file指定生成的输出文件名为file
-E只进行预处理
-S(大写)只进行预处理和编译
-c(小写)只进行预处理、编译和汇编
-v / --version查看gcc版本号
-g包含调试信息
-On n=0~3编译优化,n越大优化得越多
-Wall提示更多警告信息
-D编译时定义宏

3. 静态连接和动态连接

链接分为两种:静态链接、动态链接。

1)静态链接

静态链接:由链接器在链接时将库的内容加入到可执行程序中。

优点:

  • 对运行环境的依赖性较小,具有较好的兼容性

缺点:

  • 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间
  • 库函数有了更新,必须重新编译应用程序

2)动态链接

动态链接:连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。

优点:

  • 在需要的时候才会调入对应的资源函数
  • 简化程序的升级;有着较小的程序体积
  • 实现进程之间的资源共享(避免重复拷贝)

缺点:

  • 依赖动态库,不能独立运行
  • 动态库依赖版本问题严重

3)静态、动态编译对比

前面我们编写的应用程序大量用到了标准库函数,系统默认采用动态链接的方式进行编译程序,若想采用静态编译,加入-static参数。

3.1动态库制作与使用

静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。

按照习惯,一般以“.a”做为文件后缀名。静态库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自己定义即可
  • 后缀:.a

所以最终的静态库的名字应该为:libxxx.a

1) 静态库制作
创建静态库过程
本次静态库建立加减乘除四个函数库,即建立四个文件分别为add.c, sub.c, mul.c和div.c 。再建立实现依赖文件main.c 。
add.c

   #include <stdio.h>
   
  int add(int a, int b)
  {
      return a+b;
  }

sub.c

#include<stdio.h>

int sub(int a, int b)
{
   return a-b;
 }

mul.c

#include <stdio.h>

int mul(int a, int b)
{
    return a*b;
}

div.c

#include<stdio.h>

int div(int a, int b)
{
    if(b==0)
    {   
        return -1; 
    }   
    return a/b;
}

main.c

#include <stdio.h>

int main()
{
    int a = 10, b = 10; 
    printf("a = %d, b = %d\r\n",a ,b);
    printf("a + b = %d\r\n", add(a, b)); 
    printf("a - b = %d\r\n", sub(a, b));
    printf("a * b = %d\r\n", mul(a, b));
    printf("a / b = %d\r\n", div(a, b));
    return 0;
}

步骤1:将c源文件生成对应的.o文件

[wiggins@localhost lib]$ gcc -c add.c -o add.o
[wiggins@localhost lib]$ gcc -c sub.c -o sub.o
[wiggins@localhost lib]$ gcc -c mul.c -o mul.o
[wiggins@localhost lib]$ gcc -c div.c -o div.o

步骤2:使用打包工具ar将准备好的.o文件打包为.a文件 libtest.a

[wiggins@localhost lib]$ ar -rcs libtest.a add.o sub.o mul.o div.o

在使用ar工具是时候需要添加参数:rcs

  • r更新
  • c创建
  • s建立索引

2)静态库使用

静态库制作完成之后,需要将.a文件和头文件一起发布给用户。

假设测试文件为main.c,静态库文件为libtest.a头文件为head.h

编译命令:

[wiggins@localhost lib]$ gcc main.c -L./ -I./ -ltest -o test
[wiggins@localhost lib]$ ./test 

参数说明:

-L:表示要连接的库所在目录
-I./: I(大写i) 表示指定头文件的目录为当前目录
-l(小写L):指定链接时需要的库,去掉前缀和后缀
静态库调用

3.2. 动态库制作和使用

共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。

动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

按照习惯,一般以“.so”做为文件后缀名。共享库的命名一般分为三个部分:

前缀:lib
库名称:自己定义即可
后缀:.so

所以最终的动态库的名字应该为:libxxx.so

动态库

1)动态库制作

步骤一:生成目标文件,此时要加编译选项:-fPIC(fpic)

[wiggins@localhost lib]$ gcc -fPIC -c add.c
[wiggins@localhost lib]$ gcc -fpic -c sub.c
[wiggins@localhost lib]$ gcc -fpic -c mul.c
[wiggins@localhost lib]$ gcc -fpic -c div.c

参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。

步骤二:生成共享库,此时要加链接器选项: -shared(指定生成动态链接库)

[wiggins@localhost lib]$ gcc -shared add.o sub.o mul.o div.o -o libvoltest.so

步骤三: 通过nm命令查看对应的函数

[wiggins@localhost lib]$ nm libtest.so | grep add 00000000000006b0 T add 
[wiggins@localhost lib]$ nm libtest.so | grep sub 00000000000006c4 T sub

ldd查看可执行文件的依赖的动态库

[wiggins@localhost lib]$ ldd test linux-vdso.so.1 => (0x00007ffcf89d4000) libtest.so => /lib/libtest.so (0x00007f81b5612000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f81b5248000) /lib64/ld-linux-x86-64.so.2 (0x00005562d0cff000)

2)动态库测试

在测试之前需要告诉编译器去哪找这个动态库,所以需要设置动态库路径。方法如下,选择一种方法使用即可。

  • 方法一、拷贝自己制作的共享库到/lib或者/usr/lib(不能是/lib64目录)

  • 方法二、临时设置LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
  • 方法三永久设置,
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径

把该路径设置到~/.bashrc或者 /etc/profile文件中

[wiggins@localhost lib]$vim ~/.bashrc

最后一行添加如下内容:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wiggins/share/blog/lib

使环境变量生效

[wiggins@localhost lib]$source ~/.bashrc [wiggins@localhost lib]$./voltest
  • 方法四、将其添加到 /etc/ld.so.conf文件中

编辑/etc/ld.so.conf文件,加入库文件所在目录的路径

运行sudo ldconfig -v,该命令会重建/etc/ld.so.cache文件

[wiggins@localhost lib]$ sudo vim /etc/ld.so.conf

文件最后添加动态库路径(绝对路径)
动态库绝对路径

使生效

[wiggins@localhost lib]$ sudo ldconfig -v
  • 方法五、使用符号链接, 但是一定要使用绝对路径
[wiggins@localhost lib]$ sudo ln -s /home/wiggins/share/blog/lib/libvoltest.so /lib/libvoltest.so

完成以上一种后开始进行动态编译,引用动态库编译成可执行文件(跟静态库方式一样)

[wiggins@localhost lib]$ gcc main.c -L./ -I./ -lvoltest -o voltest
[wiggins@localhost lib]$ ./voltest 

4. GDB调试器

4.1 GDB简介

GNU工具集中的调试器是GDB(GNU Debugger),该程序是一个交互式工具,工作在字符模式。

除gdb外,linux下比较有名的调试器还有xxgdb, ddd, kgdb, ups。

GDB主要帮忙你完成下面四个方面的功能:

  1. 启动程序,可以按照你的自定义的要求随心所欲的运行程序。
  2. 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
  3. 当程序被停住时,可以检查此时你的程序中所发生的事。
  4. 动态的改变你程序的执行环境。

4.2 生成调试信息

一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:

gcc -g hello.c -o hello

g++ -g hello.cpp -o hello

如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。

4.3 启动GDB

  • 启动gdb:gdb program

program 也就是你的执行文件,一般在当前目录下。

  • 设置运行参数

set args 可指定运行时参数。(如:set args 10 20 30 40 50 )

show args 命令可以查看设置好的运行参数。

  • 启动程序

run: 程序开始执行,如果有断点,停在第一个断点处

start: 程序向下执行一行。

4.3 显示源代码

用list命令来打印程序的源代码。默认打印10行。

Ø list linenum: 打印第linenm行的上下文内容.

Ø list function: 显示函数名为function的函数的源程序。

Ø list: 显示当前行后面的源程序。

Ø list -: 显示当前行前面的源程序。

一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。

Ø set listsize count:设置一次显示源代码的行数。

Ø show listsize: 查看当前listsize的设置。

4.4 断点操作

  1. 简单断点

break 设置断点,可以简写为b

Ø b 10 设置断点,在源程序第10行

Ø b func 设置断点,在func函数入口处

  1. 多文件设置断点

C++中可以使用class::function或function(type,type)格式来指定函数名。

如果有名称空间,可以使用namespace::class::function或者function(type,type)格式来指定函数名。

Ø break filename:linenum – 在源文件filename的linenum行处停住

Ø break filename:function – 在源文件filename的function函数的入口处停住

Ø break class::function或function(type,type) – 在类class的function函数的入口处停住

Ø break namespace::class::function – 在名称空间为namespace的类class的function函数的入口处停住

3 .查询所有断点

  1. info b
  2. info break
  3. i break
  4. i b

4.5 条件断点

一般来说,为断点设置一个条件,我们使用if关键词,后面跟其断点条件。

设置一个条件断点:

b test.c:8 if Value == 5

4.6 维护断点

1)delete [range…] 删除指定的断点,其简写命令为d。

如果不指定断点号,则表示删除所有的断点。range表示断点号的范围(如:3-7)。
比删除更好的一种方法是disable停止点,disable了的停止点,GDB不会删除,当你还需要时,enable即可,就好像回收站一样。
2) disable [range…] 使指定断点无效,简写命令是dis。

如果什么都不指定,表示disable所有的停止点。

3) enable [range…] 使无效断点生效,简写命令是ena。

如果什么都不指定,表示enable所有的停止点。

4.7 调试代码

run 运行程序,可简写为r
next 单步跟踪,函数调用当作一条简单语句执行,可简写为n
step 单步跟踪,函数调进入被调用函数体内,可简写为s
finish 退出进入的函数
until 在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体,可简写为u。
continue 继续运行程序,停在下一个断点的位置,可简写为c
quit 退出gdb,可简写为q

4.8 数据查看

1)查看运行时数据

print 打印变量、字符串、表达式等的值,可简写为p

p count 打印count的值

4.9 自动显示

你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的GDB命令是display。

display 变量名
info display – 查看display设置的自动显示的信息。
undisplay num(info display时显示的编号)
delete display dnums… – 删除自动显示,dnums意为所设置好了的自动显式的编号。如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)
disable display dnums…
enable display dnums…
disable和enalbe不删除自动显示的设置,而只是让其失效和恢复。

4.10 查看修改变量的值

1)ptype width – 查看变量width的类型

type = double

2)p width – 打印变量width 的值

$4 = 13

你可以使用set var命令来告诉GDB,width不是你GDB的参数,而是程序的变量名,如:

set var width=47 // 将变量var值设置为47

在你改变程序变量取值时,最好都使用set var格式的GDB命令

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值