Linux编程
1. vi的工作方式
三种方式:命令方式、插入方式、末行方式
- 命令方式:用户一进入vi就进入命令方式,在该模式中任何键入的字符都被看成vi的命令,键入后立即执行。
- 插入方式:当用户需要输入文本时,使用某个命令,进入插入方式,才可开始输入文本。
- 末行方式:在命令方式中键入
:
就进入末行方式,在末行方式中输入命令,例如W(写)和回车,就可将编辑的内容存入文件。
三种工作方式之间转换:
- 在操作系统提示符下键入vi ,进入命令方式。
- 命令方式 --> 插入方式,键入编辑命令,如插入命令
i
、 添加命令a
、建立新行命令o
等 。 - 命令方式 --> 末行方式,只需键入
:
, 每次只执行一条,执行完毕立即回到命令方式。 - 不论在什么方式,只要键入
esc
键,就可回到命令方式。
- 命令方式 --> 插入方式,键入编辑命令,如插入命令
- 插入方式和末行方式之间不能直接切换,必须通过命令方式。
- 在命令方式,用两个
ZZ
(大写) 可以退出vi
。 - 在末行方式用
q
或q !
,还可以用wq
。
2. 进入和退出vi
2.1 进入vi
vi [filename]
- 这是编辑一个新文件或编辑一个已存在文件的方法。
- 如果是新文件,系统会创建一个名为filename的文件。
- 如果你需要对一个存在的文件进行修改,系统会把文件的内容读入vi使用缓存区供你编辑,完成后用相关命令写入源文件或写入另一个新的文件。
vi
- 这是编辑一一个 新的文件的方法。
- 在编辑完成后必须用相关命令写入一个新的文件,杏则一切编辑的动作都无效。
进入vi后光标停留在文件的第一行行首,如希望停留在其他行,可以使用如下的命令:
vi n [filename]
: 进入vi后光标位于第n行vi十[filename]
:进入vi后光标位置为文件尾vi /string [filename]
:进入vi后当前光标位置为字符串string所在行的首行。
2.2 退出vi
以原有的文件名保存编辑动作并退出。
在末行方式下退出,用以下命令:
代码语言:javascript复制:wq (保存并退出等价于:w 接 :q)
:x
在命令方式下退出,用两个大写ZZ
即可。
以新文件名保存,必须进入末行方式,使用:
代码语言:javascript复制:w newfile (保存到新文件)
:q (退出)
不保存所做过的一切编辑动作,在末行方式下退出:
代码语言:javascript复制:q!
3. vi的编辑命令
3.1 移动光标
行号:
vi的工作是在文本缓冲区中实现的,为了帮助用户观看, 可为文本的行设置一个行号, 放在文本的左侧(行首前) 可以用末行命令set来设置和取消:
代码语言:javascript复制:set number (:set nu)
:set nonumber (:set nonu)
按字符移动:
四个带箭头的方向键:各自按方向移动一个字符k、j、h、l
:各自按上下左右移动一个字符。
按行移动:
代码语言:javascript复制home # 光标移动到本行行首
0 # 行首
^ # 第一个非空白字符
$或end # 光标移动到本行行尾
- # 光标移动到上一行的行首
[行号]G # 跳转到指定行,不指定具体行号跳到文件的最后一行
gg # 首行
按字移动光标:
- w和W:将光标移动到下一个字的字首
- e和E:将光标移动到下一个字的字尾
- b和B:将光标移动到前一个字的字首
按句移动光标:
-
(
:将光标移动到上一个句子的开头 -
)
:将光标移动到下一个 句子的开头
屏幕滚动:
ctrl u
:将屏幕向文件头方向翻滚半屏ctrl d
: 将屏幕向文件尾方向翻滚半屏ctrl b
:将屏幕向文件头方向翻滚一整屏ctrl f
:将屏幕向文件尾方向翻滚一整屏
3.2 文本操作(命令方式下)
文本的插入:
i
:插入当前字符前。I
:插入当前行头a
:插入当前字符后A
:插入当前行尾o
:插入当前行的下一行O
:插入当前行的上一行
文本的删除:
x
:删除光标处的字符nx
:删除光标位置起的右n个字符X
:删除光标前的字符nX
:删除光标位置前的左n个字符dd
:删除当前行ndd
:删除当前行起的n行D或d$
:删除光标起到行尾的内容d0
:删除光标起前一个字符到行首的内容dw
:删除一个单词ndw
:删除n个单词
恢复删除:
u
: 撤销上一次的编辑动作(可多次)U
:撤销在本行所有的编辑动作.
:再次执行刚执行过的命令
修改文本:
c 或 c$
:改变光标位置起到行尾的内容nC
:改变光标位置起的n行内容。cc
:改变当前行的内容。ncc
:改变当前行起n行的内容。
文本替换:
文本替换是用新的文本替换原有的文本。
命令:r
和R
rx
:用x字符替换光标位置的字符nrx
:用x字符替换光标位置起的n个字符R
:进入替换模式,结束后按esc回到命令模式
复制与粘贴:
yy 和 Y
:复制当前行。nyy 和 nY
:复制当前行以下n行。dd
:剪切当前行。ndd
:剪切当前行以下n行。p、P
:粘贴在当前光标所在行下或行上。
移动多行:
代码语言:javascript复制:n1,n2 m k # 从n1行到n2行的文本移动到k行处,其中m是移动命令
搜索和替换:
-
/string
:向前搜索指定字符串。 搜索时忽略大小写:set ic
-
?string
:向后搜索指定字符串。 -
n,N
:向前向后搜索该字符串。 -
:%s/oldstr/newstr/g
:全文替换指定字符串 -
:n1,n2s/oldstr/newstr/g
:在n1行到n2行的范围内替换指定字符串。
4. 定义快捷键
代码语言:javascript复制:map 快捷键 命令 # 定义快捷键
:unmap 快捷键 # 删除快捷键
[例1]:
代码语言:javascript复制# 设置ctrl O为注释当前行:
:map ^O I#<esc> # ^使用ctrl v
# 设置ctrl D为取消当前行注释:
:map ^D 0x
:unmap ^O
:unmap ^D
5. gcc编译器
GNU CC (简称为 gcc)是GNU项目中符 合ANSIC标准的编译系统,能够编译用 C、C 和ObjectC等语言编写的程序。 gcc不仅功能强大,而且可以编译如 C、 C 、Object C、Java、 Fortran、 Pascal Modula-3和 Ada等多种语言,而且gcc又是一个交叉平台 编译器,它能够在当前CPU平台上为多种不同体系结构的硬件平台开发软件,因此尤其适合在嵌入式领域的开发编译。
5.1 安装(c语言中文网)
由于 Linux 操作系统的自由、开源,在其基础上衍生出了很多不同的 Linux 操作系统,如 CentOS、Ubuntu、Debian 等。这些 Linux 发行版中,大多数都默认装有 GCC 编译器(版本通常都较低)。
如果不清楚当前使用的 Linux 发行版是否已经装有 GCC 编译器,或者忘记了已安装 GCC 的版本号,可以打开命令行窗口(Terminal)并执行如下指令:
代码语言:javascript复制[root@VM-24-17-centos linux5]# gcc --version
gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-4)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
如果没有安装:
代码语言:javascript复制bash: /usr/bin/gcc: No such file or directory
5.1.1 快速安装
要知道,每个 Linux 发行版都有自己的软件包管理工具,比如 CentOS 系统的 yum 包管理器、Ubuntu 系统的 apt 包管理器等等,并且大多数 Linux 发行版都提供有 GCC 编译器的二进制软件包。因此,我们可以直接“傻瓜式”地安装 GCC 编译器(以 yum 为例):
代码语言:javascript复制yum -y install gcc
yum -y install gcc-c
通过执行这 2 条指令,就可以在 CentOS 系统中利用 gcc 命令来执行 C 语言程序,利用 g 命令来执行 C 程序。
注意:切勿认为 gcc 只能用来编译 C 语言程序,g 只能用于编译 C 程序,这是不对的。
需要注意的是,采用此方式安装的 GCC 编译器,版本通常较低。以我当前使用的 Centos 6.5 系统为例,通过执行以上 2 条指令,其安装的是 GCC 版本为 4.4.7,而当下 GCC 编译器已经迭代至 10.0.1 版本。
这意味着,如果读者使用此方式安装 GCC 编译器,需要查看 GCC 编译器的版本(通过gcc --version
指令)是否符合自己的需求。举个例子,如果读者想编译 C 11 标准下的 C 程序,则至少要安装 4.8 版本的 GCC 编译器,低版本的 GCC 编译器是不支持 C 11 标准的。
总的来说,如果读者对 GCC 编译器的版本没有要求,则推荐使用此安装方式;反之,如果读者需要安装指定版本的 GCC 编译器,则需要使用接下来介绍的安装方法。
5.1.2 手动安装
此方式需要耗费的时间较长(几个小时),但支持安装指定版本的 GCC 编译器,并适用于大多数 Linux 发行版(不同之处会有额外提示);同时,如果读者想对已安装的 GCC 编译器进行版本升级,也可以使用此方式。
和使用 yum 自动安装 GCC 编译器不同,手动安装 GCC 编译器需要提前到 GCC 官网下载指定版本的 GCC 源码安装包,读者可直接点击GCC源码包进行下载。值得一提的是,每个版本中都包含 2 种格式的压缩包,分别为 tar.gz 和 tar.xz,只是压缩格式不同,本节以 tar.gz 压缩包教大家安装 GCC 编译器。
这里以在 CentOS 系统上安装 10.1.0 最新版本的 GCC 编译器为例,下载的是 gcc-10.1.0.tar.gz 源码压缩包,整个安装过程如下:
以源码的方式安装 GCC 编译器,即手动编译 GCC 编译器的源码,需要当前系统中存在一个可用的编译器,我们可以用旧版本的 GCC 编译器来编译安装新版本的 GCC 编译器。
如果读者所用的操作系统已安装有旧版本的 GCC 编译器,则无需另行安装;反之,读者需要先运行如下命令,安装一个旧版本的 GCC 编译器:
代码语言:javascript复制yum install -y glibc-static libstdc -static
yum install -y gcc gcc-c
再次强调,不同 Linux 发行版的软件管理器也有所不同,比如 yum 仅适用于 CentOS、RedHat、Fedora 发行版;而 Ubuntu 系统需使用 apt 完成安装。
其中,第一行指令用于安装编译 C 和 C 代码所需的静态链接库;第二行指令用于安装编译 C 和 C 代码的 gcc 和 g 指令。
找到下载好的 gcc-10.1.0.tar.gz 安装包,将其解压至 /usr/local/ 目录下,解压命令为:
代码语言:javascript复制[root@bogon local]# tar -xf gcc-10.1.0.tar.gz -C /usr/local/
由此,在 usr/local/ 目录下,就生成了一个新的名为 gcc-10.1.0 的目录(也就是文件夹)。
紧接着执行如下指令,下载安装 GCC 所需要的依赖包(如 gmp、mpfr、mpc 等):
代码语言:javascript复制[root@bogon local]# cd /usr/local/gcc-10.1.0
[root@bogon gcc-10.1.0]# ./contrib/download_prerequisites
注意,一定观察此命令的执行结果,保证其确实是将 gmp、mpfr、mpc 等依赖包成功下载下来,才能继续执行下面的安装步骤。
完成以上准备工作之后,就正式进入安装 GCC 编译器的环节。首先,我们需要手动创建一个目录,用于存放编译 GCC 源码包生成的文件。执行如下命令:
代码语言:javascript复制[root@bogon local]# mkdir gcc-build-10.1.0
[root@bogon local]# cd gcc-build-10.1.0
由此,我们在 /usr/local 目录下手动创建了名为 gcc-build-10.1.0 的目录文件,并进入到该目录中。
同时,由于 GCC 编译器支持多种编程语言的编译,而实际情况中我们可能只需要编译 1~2 种编程语言,因此需要对其进行必要的配置。通过执行如下指令,可以配置 GCC 支持编译 C 和 C 语言:
代码语言:javascript复制[root@bogon gcc-build-10.1.0]# ../gcc-10.1.0/configure --enable-checking=release --enable-languages=c,c --disable-multilib
有关 configure 后跟的各个参数的含义,读者仅需要了解 --enable-languages 用于设定 GCC 编译器支持编译的编程语言的类别,例如 c、c 、java、objc、obj-c 、go 等。
在第 4 步创建好 makefile 文件之后,接下来就可以使用 make 命令来编译 GCC 源程序:
代码语言:javascript复制[root@bogon gcc-build-10.1.0]# make
注意,编译过程是非常耗时的(本机耗时 6 小时完成编译),因此如果读者选用此方式安装 GCC,则在执行 make 命令时一定要安排合适的时间。
最后,执行如下命令安装 gcc:
代码语言:javascript复制[root@bogon gcc-build-10.1.0]# make install
由此就成功安装了 10.1.0 版本的 GCC 编译器。需要注意的是,如果此时读者直接执行 gcc --version,则 gcc 版本仍会显示之前安装的版本。操作系统重启之后,GCC 版本就会自行更正过来。
5.2 gcc 编译过程
代码语言:javascript复制#include <stdio.h>
int main(){
puts("hello,world!");
return 0;
}
gcc编译过程分为4个步骤:
- 预处理( Pre-Processing )
- 编译 ( Compiling )
- 汇编 (Assembling )
- 链接 ( Linking )
5.2.1 预处理阶段
-E
:预处理的主要作用是通过预处理的内建功能对一些可预处理资源进行等价替换,最常见的可预处理资源有:文件包含、条件编译、布局控制、宏处理等。
gcc的选项,-E
可以使编译器在预处理结束时就停止编译,生成.i
文件(作用:把头文件嵌入)
gcc -E -o [目标文件] [编译文件]
# 例如
gcc -E -o hello.i hello.c
5.2.2 编译阶段
gcc的选项,-S
,生成.s
文件(作用:检查代码的规范性、是否有语法错误等,以确定代码实际要做的工作,在检查无误后,就开始把代码翻译成汇编语言。)
.s
是汇编语言原始程序
gcc -S -o hello.s hello.c
hello.s
可以直接执行。
5.2.3 汇编阶段
gcc选项,-c
,汇编阶段是把编译阶段生成的.S
文件 转换成 二进制目标代码(.o
目标文件)
gcc -c hello.s -o hello.o
5.2.4 链接阶段
完成了链接之后,gcc就可以生成可执行文件,其命令格式如下。
代码语言:javascript复制gcc hello.o -o hello
运行该可执行文件即可:
代码语言:javascript复制./hello
5.3 gcc所支持的后缀名
后缀名 | 所对应语言 | 后缀名 | 所对应语言 |
---|---|---|---|
.c | C原始程序 | .s/S | 汇编语言原始程序 |
.C/.cc/.cxx | C 原始程序 | .h | 预处理文件(头文件) |
.m | Object-C原始程序 | .o | 目标文件 |
.i | 已经经过预处理的C原始程序 | .a/so | 编译后的库文件 |
.ii | 已经经过预处理的C |
5.4 gcc 常用编译选项
- 常用选项:
选项含义-c只编译汇编不链接,生成目标文件
.o
-S只编译不链接,生成汇编代码-E只进行预处理,不做其他处理-g在可执行程序中包含标准调试信息,加了这个才可以gdb调试该文件。-o file将file文件指定为输出文件-v打印出编译器内部编译各过程的命令行信息和编译器的版本-I dir指明头文件所在位置,在头文件的搜索路径列表中添加dir目录 默认情况下标准头文件存放位置:/usr/include - 库相关选项:
选项含义-static进行静态编译,即链接静态库,禁止链接动态库-shared1. 可以生成动态库文件
2. 进行动态编译,尽可能地链接动态库,只有当没有动态库时,才会链接同名的静态库。-L dir指明库文件所在位置,在库文件的搜索路径列表中添加dir目录-l name指定库文件名称,链接名为
libname.a
(静态库)或者libname.so
(动态库)的库文件。若两个库都存在,则根据编译方法(-static/-shared)进行链接。-fPIC(-fpic)生成使用相对地址的位置无关的目标代码( Position Independent Code ) 。 然后通常使用gcc的-static 选项从该PIC 目标文件生成动态库文件默认情况下库文件的存放位置:/usr/lib
或/lib
。 - 警告和出错选项: 选项含义-ansi支持符合ANSI标准的C程序-w关闭所有警告信息-Wall允许发出gcc提供的所有有用的报警信息
- 优化选项:
选项含义-O编译优化,使得编译后的代码执行效率更高-O2数字越大, 编译优化的效果越好gcc可以对代码进行优化,它通过编译选项
-On
来控制优化代码的生成,其中n
是一个代表优化级别的整数。 对于不同版本的gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。 通常情况下,数字越大,会起到更好的优化效果,但整个编译链接的过程会变慢。
5.5 库文件的创建
库文件的分类:
- 静态库文件:
指编译链接时,把库文件的代码全部加入到可执行文件中,生成的可执行文件变大,运行时不再需要库文件,后缀一般
a
。 - 动态库文件:
编译链接时,没有把库文件的代码加入到可执行文件中,在执行的时候去访问库文件,节省系统开销,生成的文件也小,后缀一般
.so
。
静态库文件的创建(见例2):
编写源代码:xxx.c
编译成一个目标文件:xxx.o
执行命令,产生静态库文件:
代码语言:javascript复制ar -cr libxxx.a xxx.o
动态库文件的创建(见例3):
编写源代码:xxx.c
执行命令,产生动态库文件:
代码语言:javascript复制gcc -shared -fpic -o libxxx.so xxx.c
# -shared: 生成共享库
# -fpic: 产生位置无关代码
【例子1】:
头文件方式调用
./test.c
#include <stdio.h> //这个是在/usr/lib中找的
#include "mytest.h" //这个是在当前目录找的
int main(){
mytest();
printf("hello!n");
return 0;
}
./mytest.h
(头文件)
#include <stdio.h>
void mytest(){
printf("mytest() 头文件方式调用!n");
}
【例2】
静态库文件方式调用:
./test.c
#include <stdio.h>
int main(){
Mytest();
printf("hello!n");
return 0;
}
./mytest.c
#include <stdio.h>
void mytest(){
printf("mytest() 静态库方式调用!n");
}
执行:
代码语言:javascript复制# 编译
gcc -c mytest.c -o mytest.o
# 生成静态库文件
ar -cr -o libmytest.a mytest.o
编译:
代码语言:javascript复制gcc test.c -o test -L ./ -l libmytest.a # 错误
# 指明库文件名称时,要使用简写形式,例如:
# -l libmytest.a 简写为:-lmytest
# 即:
[root@VM-24-17-centos linux5]# gcc test.c -o test -L ./ -lmytest
test.c: In function ‘main’:
test.c:3:5: warning: implicit declaration of function ‘mytest’ [-Wimplicit-function-declaration]
mytest();
^~~~~~
[root@VM-24-17-centos linux5]# ./test
mytest() 静态库方式调用!
hello!
【例3】
动态库文件方式调用:
代码语言:javascript复制# 生成动态库文件
gcc -shared -fpic -o libmytest.so mytest.c
# 编译
gcc test.c -o test -L ./ -lmytest
./test
# 此时执行./test,会报错:
./test: error while loading shared libraries: libmytest.so: cannot open shared object file: No such file or directory
# 因为默认库文件在/usr/lib或者/lib中,而动态库文件是在运行时访问,在执行./test的时候,默认库文件放置位置是找不到刚刚生成的动态库的,所以运行前必须把动态库文件复制到/usr/lib目录
cp libmytest.so /usr/local/lib
./test
# 如果还报错:
vim /etc/ld.so.conf
add/usr/lib
sudo ldconfig # 刷新即可
6. gdb调试器
GDB是GNU开源组织发布的一个强大的Linux下的程序调试工具,它是一种强大的命令行调试工具。。一个合格的调试器需要有以下几项基本功能:
- 能够运行程序,设置所有能影响程序运行的参数;
- 能够让程序让指定的条件下停止。
- 能够在程序停止时检查所有参数的情况。
- 能够根据指定条件改变程序的运行。
6.1 gdb 基本用法
需要调试的程序,在编译时要使用调试选项。在默认的情况下,调试符号不会编译到程序中。调试之后,不需要重新编译程序。
代码语言:javascript复制gdb [可执行文件名]
出现
(gdb)#这里可以输入调试命令
注意:可执行文件编译的时候,必须加入参数-g,才可以使用gdb
6.2 gdb 常用命令
进入gdb调试界面后,可以输入以下命令:
list
:显示程序内容,默认一次显示10行,如需继续,继续回车即可或者继续list
。
list函数名
:显示函数对应内容
list行号
:显示该行周围的源代码
Set listsize 行数
:设置显示代码的行数
Show listsize
:显示当前listsize的设置
listfirst,last
:显示first和list之间的内容
next
:执行一行源代码但不进入函数内部
step
:执行一行源代码并进入函数内部。
continue
:继续运行,到下一个断点停止
run
:执行当前被调试的程序。
quit
:退出gdb 。
break n
:在第n行设置断点,这将使程序执行到这里时被挂起。
break n 条件
:当条件满足是,在该行断点生效。
break 12 if i==12
break 函数名
:在函数的起始位置设置断点,这将使程序执行到这里时被挂起。
info break
:显示断点信息。
Delete n
:去除第n号断点
Disable n
:暂停使用第n号断点
Enable n
:启动第n号断点
Break
:从后向前清除断点
print 变量名
:显示变量的值
file 文件名
:如果直接执行gdb,没有跟文件名,可以通过这个调入想要调试的可执行文件。
kill
:终止正在调试的程序
watch
:使你能监视一个变量的值而不管它何时被改变。
make
使你能不退出gdb,就可以重新产生可执行文件。
shell
:使你能不离开 gdb 就执行UNIX shell命令
Ctrl c
:发信号量中断当前操作
注意: 所有命令均可以使用命令的首字母来执行,例如
list
可以用l
执行。
7. make 工程管理
问题:有多个源文件的时候,如何生成一个可执行文件?
方法1:
代码语言:javascript复制gcc -Wall -o mytest test1.c test2.c test3.c
# -Wall 允许发出gcc提供的所有有用的报警信息
方法2:
代码语言:javascript复制gcc -Wall -c test1.c
gcc -Wall -c test2.c
gcc -Wall -c test3.c
gcc -o mytest test1.o test2.o test3.o
可以发现是比较繁琐的,这时候就引入了make工程管理:
Make工程管理器是Linux下的一个“自动编译管理器”,“自动”是指它能够根据文件的时间戳,自动发现更新过的文件而减少程序编译的工作量。能够通过读入Makefile文件的内容来执行大量的编译工作,用户只需要编写一次简单的编译语句即可。Make工具大大提高了实际项目的工作效率,几乎所有Linux下的项目编程都会涉及它。
即:通过Makefile 文件,制定编译规则,根据时间戳,判断文件是否进行过修改。
Makefile编译规则格式:
代码语言:javascript复制目标名称: 依赖文件(一般是.o文件,如果没有,就会找对应的.c文件,)
<tab>命令
执行格式:
代码语言:javascript复制make 目标名称(如果省略,自动找第一个目标执行)
例如:
代码语言:javascript复制all: main.o foo1.o foo2.o
# 虽然没有.o文件,但是会自动根据依赖文件是.c,就会根据.c文件自动生成对应名字的.o目标文件
gcc main.o foo1.o foo2.o -o all
# 伪目标:不需要依赖文件,只执行命令
clean:
rm -f *.o
代码语言:javascript复制make all
此时我们如果修改了foo1.c,按以前的方法,还需要重新编译,但是引入make工程管理,只需再次make就会自动编译。
代码语言:javascript复制make clean # 执行尾目标