侧边栏壁纸
博主头像
如此肤浅

但行好事,莫问前程!

  • 累计撰写 52 篇文章
  • 累计创建 6 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

Linux 系统编程 | 1. Linux 基础

如此肤浅
2022-06-16 / 0 评论 / 0 点赞 / 46 阅读 / 15,710 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-22,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Linux 基础

Linux 系统目录结构

Unix/Linux 采用树状结构的文件系统,它由目录和目录下的文件一起构成,其目录结构如下:
image-1655376102660

  • /:根目录,一般根目录下只存放目录,在Linux下有且只有一个根目录。所有的东西都是从这里开始。当你在终端里输入“/home”,你其实是在告诉电脑,先从/(根目录)开始,再进入到home目录。
  • /bin /usr/bin:可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。
  • /boot:放置linux系统启动时用到的一些文件,如Linux的内核文件:/boot/vmlinuz,系统引导管理器:/boot/grub。
  • /dev:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,如使用cat命令读取/dev/input/mice 即是在读取鼠标事件。
  • /etc:系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有 /etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d。
  • /home:系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,表示当前用户的家目录,edu 表示用户 edu 的家目录。
  • /lib /usr/lib /usr/local/lib:系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助。
  • /mnt /media:光盘默认挂载点,通常光盘挂载于 /mnt/cdrom 下,也不一定,可以选择任意位置进行挂载。
  • /opt:给主机额外安装软件所摆放的目录。
  • /proc:此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有 /proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/* 等。
  • /root:系统管理员root的家目录(宿主目录)。
  • /sbin /usr/sbin /usr/local/sbin:放置系统管理员使用的可执行命令,如fdisk、shutdown、mount 等。与 /bin 不同的是,这几个目录是给系统管理员 root使用的命令,一般用户只能"查看"而不能设置和使用。
  • /tmp:一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下。
  • /srv:服务启动之后需要访问的数据目录,如 www 服务需要访问的网页数据存放在 /srv/www 内。
  • /usr:应用程序存放目录,/usr/bin 存放应用程序,/usr/share 存放共享数据,/usr/lib 存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local: 存放软件升级包。/usr/share/doc: 系统说明文件存放目录。/usr/share/man: 程序说明文件存放目录。/usr/include:存放头文件。
  • /var:放置系统执行过程中经常变化的文件,如随时更改的日志文件 /var/log,/var/log/message:所有的登录文件存放目录,/var/spool/mail:邮件存放的目录,/var/lib/mysql:MySQL数据库成功安装后,使用的库、表相关文件,存放在该目录下。

Linux 基本命令

Linux 命令格式

command [-options] [parameter1]  ...
  • command:命令名,相应功能的英文单词或单词的缩写
  • options:选项,可用来对命令进行控制,可以省略
  • parameter1:传给命令的参数,可以是零个一个或多个
    image-1655375473466

–help

一般 Linux 命令自带的帮助信息。可以使用--help查看。如:想查看命令“ls”的用法:

ls --help

并不是所有命令都自带这个选项。

man 手册

共有 9 章:
image-1655376771835

  • 命令格式:

    man [选项] 命令名
    
    # 使用man命令时最后带上章节,如:
    man 3 printf
    
  • man 手册中的功能键:

功能键 功能
空格键 显示手册页的下一屏
Enter键 一次滚动手册页的一行
b 回滚一屏
f 前滚一屏
q 退出man手册
h 列出所有功能键
/test 在手册中搜索字符串test

切换目录:cd

命令 含义
cd 切换到当前用户的主目录(/home/用户目录)
cd ~ 切换到当前用户的主目录(/home/用户目录)
cd . 切换到当前目录
cd … 切换到上级目录
cd - 切换到上一个进入的目录

目录操作:mkdir、rmdir

# 在当前目录下创建目录dir1
mkdir dir1

# 在tmp目录下创建目录dir2
mkdir /tmp/dir2

# 在当前目录下创建目录树dir1/dir2/dir3
mkdir -p dir1/dir2/dir3

# 删除空目录(怎么使用mkdir创建的,就怎么使用rmdir删除)
rmdir 路径

查看文件信息:ls

文件分类

通常,Unix/Linux 系统中常用的文件类型有 7 种:

  1. 普通文件 -
    普通文件是计算机操作系统用于存放数据、程序等信息的文件,一般都长期存放于外存储器(磁盘、磁带等)中。普通文件一般包括文本文件、数据文件、可执行的二进制程序文件等。
    在 Unix/Linux 中可以通过file命令来查看文件的类型。如果文件后面携带文件名,则查看指定文件的类型,如果携带通配符“*”,则可以查看当前目录下的所有文件的类型。

  2. 目录文件 d
    Unix/Linux系统把目录看成一种特殊的文件,利用它构成文件系统的树型结构。
    目录文件只允许系统管理员对其进行修改,用户进程可以读取目录文件,但不能对它们进行修改。
    每个目录文件至少包括两个条目,“…”表示上一级目录,“.”表示该目录本身。

  3. 管道文件 p
    管道文件也是 Unix/Linux 中较特殊的文件类型,这类文件多用于进程间的通信。

  4. 链接文件 l
    类似于 windows 下的快捷方式,链接分为软链接(符号链接)和硬链接。

  5. 设备文件(字符设备 c、块设备 b)
    Unix/Linux 系统把每个设备都映射成一个文件,这就是设备文件。它是用于向 I/O 设备提供连接的一种文件,分为字符设备和块设备文件。
    字符设备的存取以一个字符为单位,块设备的存取以字符块为单位。每一种 I/O 设备对应一个设备文件,存放在 /dev 目录中,如行式打印机对应 /dev/lp,第一个软盘驱动器对应 /dev/fd0。

  6. 套接字文件 s

ls 用法

ls 常用选项:

选项 含义
-a 显示指定目录下所有子目录与文件,包括隐藏文件
-l 以列表方式显示文件详细信息
-h 配合 -l 以人性化方式显示文件大小

例如:
image-1655450642928

创建文件:touch

若文件不存在,则创建该文件;若该文件存在,则修改该文件的时间戳。

拷贝:cp

cp 命令的功能是将给出的文件或目录复制到另一个文件或目录中。常用选项如下:

选项 含义
-a 该选项通常在复制目录时使用,它保留链接、文件属性,并递归地复制目录,简单而言,保持文件原有属性
-r 若给出的源文件是目录文件,则cp将递归复制该目录下的所有子目录和文件,目标文件必须为一个目录名
-i 交互式复制,在覆盖目标文件之前将给出提示要求用户确认
-v 显示拷贝进度

删除文件或目录:rm

可通过 rm 删除文件或目录。rm 命令删除的文件、目录很难恢复!为了防止文件误删,可在 rm 后使用 -i 参数以逐个确认要删除的文件。 常用选项如下:

选项 含义
-i 以交互式方式执行
-f 强制删除,忽略不存在的文件,无需提示
-r 递归地删除目录下的内容,删除文件夹时必须加此参数

移动或重命名:mv

用户可以使用 mv 命令来移动文件或目录,也可以给文件或目录重命名。常用选项如下:

选项 含义
-i 以交互式方式执行,在覆盖时会给出提示
-f 禁止交互方式,如有覆盖也不会给出提示
-v 显示移动进度

显示文件全部内容:cat

cat 将文件内容一次性输出到终端。

缺点: 终端显示的内容有限,如果文件太长无法全部显示。

# 查看/etc/passwd的内容
cat /etc/passwd

# 查看/etc/passwd的内容,并输出所有行的编号
cat -n /etc/passwd

# 查看/etc/passwd的内容,并给非空行编号
cat -b /etc/passwd

# 查看/etc/passwd的所有内容(每行后面的“换行符”用“$”表示)
cat -A /etc/passwd

分页显示文件内容:less

less 命令将文件内容分页显示到终端,可以自由上下浏览,j 是向下浏览,k 是向上浏览,q 退出。

显示文件头部的内容:head

head 命令从文件头部开始查看前 n 行的内容。如果没有指定行数,默认显示前 10 行内容。

# 查看/etc/passwd前10行的内容
head /etc/passwd

# 查看/etc/passwd前10个字节的内容
head -c /etc/passwd

# 查看/etc/passwd前5行的内容
head -n 5 /etc/passwd
head -5 /etc/passwd

# 查看/etc/passwd前10行的内容,但打印头信息
head -v /etc/passwd

显示文件尾部的内容:tail

从文件尾部向上查看最后 n 行的内容。如果没有指定行数,默认显示最后 10 行内容。具体用法与 head 相同。

查找符合条件的文件:find

按文件名查询:-name

# 命令:
find 路径 -name “文件名”

# 例如:
find ./ -name *.out

按文件大小查询:-size

# 命令:
find 路径 -size 范围

# 大于:用“+”表示,如“+100k”(k必须小写)
# 小于:用“-”表示,如“-100M”(M必须大写)
# 等于:不用加符号

# 例如:
find ./ -size +100k # 查询大于100k的文件
find ./ -size +100k -size -100M # 查询大于100k小于100M的文件

按文件类型查询:-type

  • f → 普通文件(而不是用-)
  • d → 目录
  • l → 符号链接
  • b → 块设备
  • c → 字符设备
  • s → socket文件,网络套接字
  • p → 管道
# 查找普通的文件
find ./ -type f

文本模式查找:grep

# 命令格式:(需要引号)
grep [-选项] "搜索的内容串" 文件名

常用选项如下:

选项 含义
-v 显示不包含文本的所有行(相当于求反)
-n 显示匹配行及行号
-i 忽略大小写
-r 递归查找目录下的文件

例如:

# 查找/etc/passwd中含root的行
grep "root" /etc/passwd
# 查找/etc/passwd中不含root的行
grep -v "root" /etc/passwd

管道:|

使用管道将一个命令的输出作为另一个命令的输入。例如:
image-1655468312328

归档(打包)文件:tar

计算机中的数据经常需要备份,tar 是 Unix/Linux 中最常用的备份工具,此命令可以把一系列文件归档到一个大文件中,也可以把档案文件解开以恢复数据。

归档和压缩不同!!!

# 命令格式:(tar比较特殊,选项可以省略”-“)
tar [-选项] 归档后的文件名 要归档的文件

常用选项如下:

选项 含义
-c 生成档案文件,创建打包文件
-v 列出归档解档的详细过程,显示进度
-f 指定档案文件名称,f 后面一定是 .tar 文件名,放选项最后
-t 列出档案中包含的文件
-x 解开档案文件
-z 指定压缩格式为:file.tar.gz
# 最常用的3种组合
tar -cvf # 归档
tar -xvf # 解归档
tar -tvf # 查看归档文件中的包含的文件

image-1655511573404

压缩:gzip

常与 tar 结合使用实习文件打包、压缩!
tar 只负责打包文件,但不压缩,用 gzip 压缩 tar 打包后的文件,其扩展名一般用 xxx.tar.gz 。

# 命令格式:(gzip后不能跟目录)
# 如果不加选项,默认是压缩
gzip [-选项] 被压缩的文件

常用选项如下:

选项 含义
-d 解压(gzip -d 等效于 gunzip)
-f 压缩所有子目录

image-1655512298373

gzip 配合 tar 使用:

# 归档压缩
tar -czvf test.tar.gz 需要压缩的文件
# 解压解归档
tar -xzvf test.tar.gz
# 解压解归档到目录/tmp
tar -xzvf test.tar.gz -C /tmp

image-1655512702578

修改文件权限:chmod

chmod 修改文件权限有两种格式:字母法、数字法。

  • 字母法

    # 命令格式:
    chmod u\g\o\a +\-\= 文件
    
    # 例如:
    # 对于test.txt,给用户所属组添加执行权限
    chmod g+x test.txt
    # 对于test.txt,给将其他用户的权限设为可读可执行
    chmod o=rx test.txt
    # 对于test.txt,将所属用户权限设为可读可写,将用户所属组权限设为可读,将其他用户权限设为可读
    chmod u=rw,g=r,o=r test.txt # 不能省略逗号
    
    u\g\o\a 含义
    u user,表示该文件的所有者
    g group,表示与该文件所有者属于同用户组
    o other,表示其他人
    a all,表示这三者都是
    +\-\= 含义
    + 增加权限
    - 撤销权限
    = 设定权限
  • 数字法
    “rwx” 这些权限也可以用数字来代替。

    # 命令格式:
    chmod 数字组合 文件名
    
    # 例如:
    # 对于test.txt,user可读可写可执行,group可读可执行,other可读
    chmod 754 test.txt
    # 对于test.txt,user可读
    chmod 400 test.txt
    
    # 改变testDir目录下所有的权限
    chmod -R 777 test/
    
    r\w\x- 对应数字代号
    r 读权限,代号为4
    w 写权限,代号为2
    x 执行权限,代号为1
    - 没有任何权限,代号为0

修改文件所有者:chown

# 命令格式:
chown 用户名 文件或目录名

# 将test.txt所属用户修改为root用户
sudo chown root test.txt
# 将test.txt所属用户组修改为root组
sudo chown :root test.txt	# “:”不可省略
# 同时修改所属用户和用户组为root
sudo chown root:root test.txt	

软件安装与卸载

以 Ubuntu 为例!!!

  • 在线安装

    # 更新软件包列表
    sudo apt update 
    # 安装xxx软件
    sudo apt install xxx
    # 卸载xxx软件
    sudo apt remove xxx
    # 清理安装包
    sudo apt clean
    
  • 离线安装
    在 Ubuntu 下安装 deb 格式的安装包。

    # 软件安装
    sudo dpkg -i xxx.deb
    # 软件卸载(也可以用上面的方法卸载)
    sudo dpkg -r 软件名
    

重定向:>、>>

重定向一般用在 shell 脚本中。

Linux 允许将命令执行结果重定向到一个文件,本应显示在终端上的内容保存到指定文件中,方便后续查看。

>:以覆盖的方式重定向
>>:以追加的方式重定向

# 标准错误输出需要使用“2>”进行重定向
# 假设使用了错误的命令“lss”,则会在终端输出错误信息,若重定向到文件error:
lss 2> error
# 将标准错误输出重定向到黑洞文件/dev/null
lss 2> /dev/null
# 将标准输出和标准错误输出都重定向到黑洞文件/dev/null
ls dddd &> /dev/null

image-1655519177873

目录树:tree

使用前需要先安装 tree。

# 显示所在目录的完整目录树
tree
# 目录树只显示到2层
tree -L 2

链接:ln

  • 软链接
    软链接不占用磁盘空间,源文件删除则软链接失效。源文件删除则软链接失效。源文件搬移也可能造成连接失效,因此建议使用绝对路径法创建软连接。

    # 命令格式:
    ln -s 源文件 链接文件
    
    # 创建软链接a_link指向文件a
    ln -s a a_link
    
  • 硬链接
    硬链接只能链接普通文件,不能链接目录。

    # 命令格式:
    ln 源文件 链接文件
    
    # 创建硬链接a_link指向文件a
    ln a a_link
    

vi(vim)编辑器

vi 编辑器是 Unix 系统中最常见的基础文本编辑器。Linux 下升级为 vim 编辑器,它不仅兼容 vi 的所有指令,而且还有一些新的特性,例如 vim 可以撤消无限次、支持关键词自动完成、可以用不同的颜色来高亮你的代码。vim 普遍被推崇为类 vi 编辑器中最好的一个。出于历史的原因使用时大家习惯性的统一简称 vi。

vi 有三种工作模式:命令模式、文本输入模式、末行模式。

切换编辑模式

命令模式下执行!

按键 功能
i 光标位置当前处插入文字
I 光标所在行首插入文字
o 光标位置下方开启新行
O 光标位置上方开启新行
a 光标位置右边插入文字
A 光标所在行尾插入文字
s 以删除一个字符为条件,切换工作模式
S 以删除一行为条件,切换工作模式

光标移动

命令模式下执行!

按键 功能
Ctrl + f 向前滚动一屏
Ctrl + b 向后滚动一屏
gg 到文件第一行行首
G 到文件最后一行行首
nG 或 ngg 到指定行,n 为目标行数
0(数字) 光标移动到行首
$ 光标移动到行尾
l 向右移动光标
h 向左移动光标
k 向上移动光标
j 向下移动光标
^ 光标移动到当前行第一个有效字符位置)

复制粘贴

命令模式下执行!

按键 功能
yy 复制当前行
nyy 复制从当前行开始的 n 行
p 粘贴到光标位置的下一行
P(大写) 粘贴到光标位置的上一行

区域选择复制粘贴:
1. 选择: 命令模式下,将光标移动至待复制内容首字符,按 v 进入可视模式,使用 hjkl 移动光标,选中区域
2. 复制: y
3. 粘贴:p 将内容粘贴至光标后,P 向前粘

剪切(删除)

命令模式下执行!

按键 功能
[n]x 删除光标后 n 个字符
[n]X 删除光标前 n 个字符
D 删除光标所在位置到此行尾的字符
[n]dd 删除从当前行开始的 n 行
dw 删除光标所在位置到该单词结尾的字符
d0 删除光标前本行所有内容,不包含光标所在字符

撤销和恢复

命令模式下执行!

按键 功能
. 执行上一次的操作
u 撤销上一次的操作
Ctrl + r 恢复撤销·

查找

命令模式下执行!

按键 功能
/字符串 查找指定字符串(n、N查找下一个)

替换

命令模式下执行!

按键 功能
r 替换当前字符
R 替换当前行光标后的字符

保存退出

末行模式下执行!

按键 功能
:wq 保存退出
:x(小写) 保存退出
:X(大写) 加密
:w filename 保存到指定文件
:q 退出,若文件被修改但没保存不允许退出
:q! 强制退出,不报错

替换

末行模式下执行!

按键 功能
:s/abc/123/ 光标所在行的第一个abc被替换为123
:s/abc/123/g 光标所在行的所有abc被替换为123
:1,10s/abc/123g 将1至10行所有的abc全部替换为123
:%s/abc/123/g 当前文件的所有abc替换为123
:%s/abc/123/gc 同上,但每次替换需要用户确认

分屏

末行模式下执行!

按键 功能
:sp 水平分屏
:vsp 垂直分屏
Ctrl + w + w 切换光标所在分屏
:wqall 可以一次保存退出所有分屏
:sp 文件名 将当前文件和另一个文件水平分屏
:vsp 文件名 将当前文件和另一个文件垂直分屏
vim -O a.c b.c 打开的时候就垂直分屏 a.c 和 b.c
vim -o a.c b.c 打开的时候就水平分屏 a.c 和 b.c
:!shell命令 在 vim 的末行模式中执行 shell 命令。如查看 man 手册(:!man 3 printf)

行号

末行模式下执行!

按键 功能
:set nu 显示行号
:set nonu 不显示行号

GCC 编译器

gcc 编译器从拿到一个 c 源文件到生成一个可执行程序,中间一共经历了四个步骤:
image-1655624798705

四个步骤并不是 gcc 独立完成的,而是在内部调用了其他工具,从而完成了整个工作流程:
image-1655624830725

# 编译命令格式:
gcc [options] file ...
g++ [options] file ...

# 命令、选项和源文件之间使用空格分隔;
# 一行命令可以有0个、1个或多个选项;
# 文件名可以使用绝对路径,也可使用相对路径;
# 如果命令中不包含输出可执行文件的文件名,Linux 平台会默认使用 a.out

常用选项如下:

选项 含义
-o file 指定生成的输出文件名为 file
-E 只进行预处理
-S(大写) 只进行预处理和编译
-c(小写) 只进行预处理、编译、·汇编
-v 查看 gcc 版本号
-g 包含调试信息
-On(n=0~3) 编译优化,n 越大优化得越多
-Wall 提示更多警告信息
-Wall 将警告信息当作错误处理
-D 编译时定义宏
# 显示所有的警告信息
gcc -Wall hello.c
# 将警告信息当作错误处理
gcc -Wall -Werror hello.c
  • 选项-D的案例:
    #include <stdio.h>
    
    int main()
    {
        printf("SIZE: %d", SIZE);
        return 0;
    }
    
    执行结果如下:
    ubuntu@VM-12-7-ubuntu:~/test$ gcc 1.c -DSIZE=10
    ubuntu@VM-12-7-ubuntu:~/test$ ./a.out 
    hello 10
    ubuntu@VM-12-7-ubuntu:~/test$
    

静态链接、动态链接

静态链接

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

  • 优点:
    ① 对运行环境的依赖性较小,具有较好的兼容性。
  • 缺点:
    ① 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间。
    ② 库函数有了更新必须重新编译应用程序。

动态链接

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

  • 优点:
    ① 在需要的时候才会调入对应的资源函数。
    ② 简化程序的升级;有着较小的程序体积。
    ③ 实现进程之间的资源共享(避免重复拷贝)。
  • 缺点:
    ① 依赖动态库,不能独立运行。
    ② 动态库依赖版本问题严重。

静态、动态链接对比

测试程序如下:

#include <stdio.h>
int main()
{
	printf("hello world\n");
    return 0;
}

编译时采用动态链接和静态链接:

# 不加任何选项默认是动态链接
gcc hello.c -o hello_share
# 静态链接时加选项 -static
gcc -static hello.c -o hello_static

结果如下:
image-1655710164256

制作静态库

静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。按照习惯,一般以”.a“做为文件后缀名。静态库的命名一般分为三个部分:① 前缀 lib;② 库名称:自己定义即可;③ 后缀:.a 。 所以最终的静态库名字应该为:libxxx.a 。

  • 制作步骤一:
    将 c 源文件生成对应的 .o 文件:

    gcc -c add.c -o add.o
    gcc -c sub.c -o sub.o
    
  • 制作步骤二:
    使用打包工具 ar 将准备好的 .o 文件打包为 .a 文件 libtest.a:

    # 命令格式:
    ar -rcs 静态库名称 .o文件
    
    # 使用 ar 工具时需要使用选项 -rcs
    # r更新、 c创建、s建立索引
    ar -rcs libtest.a sub.o add.o
    

image-1655711761054

  • 使用静态库:
    静态库制作完成之后,需要将 .a 文件和头文件一起发布给用户。假设测试文件为 main.c,静态库文件为 libtest.a 头文件为 sub.h 和 add.h。

    编译命令为:

    # -I:指出头文件所在位置
    # -L:指出静态库所在位置
    # -l:指定链接时需要的库,去掉前缀 lib 和后缀 .a
    gcc main.c -I./ -L./ -ltest -o test
    

image-1655712522004

制作动态库

共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。 动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。为什么需要动态库,其实也是静态库的特点导致。动态库的命名一般分为三个部分:① 前缀 lib;② 库名称:自己定义即可;③ 后缀:.so 。 所以最终的静态库名字应该为:libxxx.so

  • 制作步骤一:
    生成目标文件,此时要加编译选项-fpic,其意思是创建与位置无关的编译程序,为了能够在多个应用程序间共享(pic,Position Independent Code)。

    # 生成 sub.o
    gcc -fpic -c sub.c
    # 生成 add.o
    gcc -fpic -c add.c
    
  • 制作步骤二:
    生成共享库,此时要加链接器选项-shared,其作用是指定生成动态链接库。

    # 库的名字为 test,库文件的名字为 libtest.so
    gcc -shared add.o sub.o -o libtest.so
    
  • 使用动态库:
    动态库制作完成之后,需要将 .so 文件和头文件一起发布给用户。

    编译(与静态库相同):

    gcc main.c -I./ -L./ -ltest -o test
    

    执行编译生成的可执行文件时会报错:
    image-1655714626642

    在执行程序的时候是如何找到共享库文件的呢?
    ① 当系统加载可执行代码时候,能够知道其所依赖的库的名字(编译的时候告诉了编译器动态库的名字),但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
    ② 对于 elf 格式的可执行程序,是由 ld-linux.so* 来完成绝对路径查找的,它先后搜索 elf 文件的 DT_RPATH 段、环境变量 LD_LIBRARY_PATH、/etc/ld.so.cache 文件列表、/lib/和/usr/lib目录,找到库文件后将其载入内存。

    如何让系统找到制作的动态库?
    ① 临时设置 LD_LIBRARY_PATH:

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
    

    image-1655715696756
    此方式只在该终端生效,在其它终端以及系统重启后失效,再次运行可执行文件也会报错!

    ② 永久设置:
    把 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径,添加到 ∼/.bashrc 或者 /etc/profile 文件中。

    ③ …(一共有5种方式)

GDB 调试器

GDB(GNU Debugger)是 GCC 的调试工具。GDB主要有下面四个方面的功能:

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

生成调试信息

一般来说 GDB 主要调试的是C/C的程序。要调试 C/C 的程序,首先在编译时,我们必须要把调试信息加到可执行文件中,使用-g

gcc -g test.c -o a.out
g++ -g test.c -o a.out

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

启动 GDB

  • 启动 gdb:

    # 进入 a.out 的调试
    gdb a.out
    
  • 设置运行参数

    # 设置运行时的参数
    set args 参数 
    
    # 查看设置好的参数
    show args
    

    image-1655728569494

  • 启动程序

    # 程序开始执行,如果有断点就停在第一个断点处
    run
    
    # 程序向下执行一行
    start
    

显示源码

在 gdb 调试界面,用 list 命令来打印程序的源代码,默认打印 10 行。

# 打印当前行后面的 10 行
list

# 打印当前行前面的 10 行
list -

# 打印第 lineNum 行上下的内容
list lineNum

# 打印函数 function 的源程序
list function

# 设置一次显示源代码的行数为 count
set listsize count # 如:set listsize 20

# 显示当前 listsize 的设置值
show listsize

设置断点

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

    # 在源程序第 10 行设置断点
    b 10
    
    # 在函数 func 入口处设置断点
    b func 
    
  • 多文件设置断点
    C++ 中可以使用 class::function 或 function(type,type) 格式来指定函数名。如果有名称空间,可以使用 namespace::class::function 或者 function(type,type) 格式来指定函数名。

    # 在源文件 filename 的第 lineNum 行设置断点
    break filename:lineNum
    
    # 在源文件 filename 的函数 function 入口处设置断点
    break filename:function
    
    # 在类 class 的成员函数 function(...) 入口处设置断点
    break class::function(type,type)
    
    # 在命名空间为 namespace 的类 class 的
    # 成员函数 function(...) 入口处设置断点
    break namespace::class::function(type,type)
    
  • 查看所有断点

    # 下面 4 个命令功能一样,无差别
    info b
    info break
    i break
    i b
    

    image-1655729520039

条件断点

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

# 在 test.c 的第8行,i 取值为5时停住
b test.c:8 if i==5

维护断点

# delete 删除指定的断点,其简写命令为 d。
# 如果不指定断点号,则表示删除所有的断点。
delete 			# 删除所有断点
delete [2-5] 	# 删除断点2-5
delete 3 		# 删除断点3

# disable 禁用断点,其简写命令为 dis。
# 比删除更好的一种方法是 disable 停止点,disable 了的停止点,
# GDB 不会删除,当你还需要时,enable 即可,就好像回收站一样。
# 如果什么都不指定,表示 disable 所有的停止点。
disable [2-5]
disable 3

# enable 使无效断点生效,简写命令是ena。
# 如果什么都不指定,表示enable所有的停止点。
enable [2-5]
enable 3

调试代码

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

数据查看

在运行时查看数据,print 打印变量、字符串、表达式等的值,可简写为 p。

# 打印 i 此时的取值
p i 

自动显示

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

# 自动显示指定变量的值
display 变量名 # 如:display i

# 查看 display 设置的自动显示的信息
info display

# 不显示编号为 num 的 display 信息
undisplay num

# 删除自动显示,nums 意为所设置好了的自动显式的编号。
# 如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)
delete display nums

# disable 和 enalbe 不删除自动显示的设置,而只是让其失效和恢复
disable display nums
enable display nums

查看、修改变量的值

# 查看变量 i 的类型(查询结果如:type=int)
ptype i

# 打印变量 i 的值
p i

# 将变量 i 的值设置为47
# 你可以使用set var命令来告诉GDB,i 不是 GDB 的参数,而是程序的变量名
set var i=47  

Makefile

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 就像—个 Shell 脚本一样,其中也可以执行操作系统的命令。

Makefile带来的好处就是―—“自动化编译",一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令。

Makefile 文件命名规则:makefile 和 Makefile 都可以,推荐使用 Makefile。

1条规则

目标:依赖文件列表
<Tab>命令列表

Makefile 基本规则三要素:
① 目标:通常是要产生的文件名称。目标可以是可执行文件或其它 obj 文件,也可是一个动作的名称。
② 用来输入从而产生目标的文件;一个目标文件通常有几个依赖文件(也可以没有)。
③ make 执行的动作,一个规则可以含几个命令(也可以没有);有多个命令时,每个命令占一行。

例如:

all:test1 test2
    echo "hello all"

test1:
    echo "hello test1"

test2:
    echo "hello test2"

执行结果为:
image-1655773787148

3个自动变量

在 Makefile 中使用变量有点类似于 c 语言中的宏定义,使用该变量相当于内容替换,使用变量可以使 Makefile 易于维护,修改内容变得简单。

  • 自定义变量
    变量定义:变量名=变量值
    引用变量:$(变量名) 或 $

    【例如下面:第三个版本的 Makefile】

  • makefile 提供的变量
    除了使用用户自定义变量,makefile 中也提供了一些变量(变量名大写)供用户直接使用,我们可以直接对其进行赋值。

    CC=gcc
    CPPFLAGS:C 预处理的选项,-I(大写的 i)
    CFLAGS:C 编译器的选项,-Wall -g -c
    LDFLAGS:链接器选项,-L -l(小写的 L)

  • 3个自动变量
    自动变量只能在规则中使用!!!

    $^:表示规则中的所有依赖条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项
    $@:表示规则中的目标
    $<:表示规则中的第一个依赖条件

    【例如下面:第四个版本的 Makefile】

  • 模式规则
    示例:

    # 模式匹配,所有的 .o 都依赖对应的 .c
    %.o:%.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) &< -o $@
    

    【例如下面:第五个版本的 Makefile】

2个函数

makefile 中的函数有很多,在这里只记录两个最常用。

  • wildcard
    查找指定目录下的指定类型的文件
    # 找到当前目录下所有后缀为.c的文件,赋值给src
    src = $(wildcard *.c) 
    
  • patsubst
    匹配替换
    # 把src变量里所有后缀为 .c 的文件替换成 .o
    obj = $(patsubst %.c,%.o, $(src)) 
    

【例如下面:第六个版本的 Makefile】

Makefile 中的伪目标

clean 的用途:清除编译生成的中间 .o 文件和最终目标文件。

如果当前目录下有同名 clean 文件,则make clean 不执行 clean 对应的命令,解决方式是声明伪目标。声明目标为伪目标之后,makefile 将不会判断该目标是否存在或者该目标是否需要更新,会无条件执行这个命令。

# 伪目标声明
 .PHONY:clean

【例如下面:第七个版本的 Makefile】

make 命令格式

make 是一个命令工具,它解释 Makefile 种的规则。

# 命令格式:
make [-f filename][options][targets]
  • [-f filename]
    ① make 默认在工作目录中寻找名为 GNUmakefile、makefile、Makefle 的文件作为 makefile 输入文件
    ② -f 可以指定以上名字以外的文件作为 makefile 输入文件

  • [options]
    ① -v:显示 make 工具的版本信息
    ② -w:在处理 makefile 之前和之后显示工作路径
    ③ -C dir:读取 makefile 之前,改变工作路径至 dir 目录
    ④ -n:只打印要执行的命令但不执行
    ⑤ -s:执行但不显示执行的命令

  • [targets]
    ① 若使用 make 命令时没有指定目标,则 make 工具默认会实现 makefile 文件内的第一个目标,然后退出
    ② 指定了 make 工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)(如:make clean)

例如:
image-1655774859097

Makefile 示例

测试程序:test.c、add.c、sub.c

  • 最简单的 Makefile

    test:add.c sub.c test.c
    	gcc add.c sub.c test.c -o test
    

    缺点:效率低,修改一个文件,所有文件都要编译一次。

  • 第二个版本的 Makefile

    test:add.o sub.o test.o
    	gcc add.o sub.o test.o -o test
        
    add.o:add.c
    	gcc -c add.c -o add.o
          
    sub.o:sub.c
        gcc -c sub.c -o sub.o
     	
    test.o:test.c
        gcc -c test.c -o test.o
    

    此时若修改了一个文件,编译时只需要重新编译这个被改变的文件。

  • 第三个版本的 Makefile

    OBJS = add.o sub.o test.o
    test:$(OBJS)
    	gcc $(OBJS) -o test
        
    add.o:add.c
    	gcc -c add.c -o add.o
          
    sub.o:sub.c
        gcc -c sub.c -o sub.o
     	
    test.o:test.c
        gcc -c test.c -o test.o
        
    clean:
    	rm -rf $(OBJS) test
    

    使用了自定义变量,使 Makefile 更精简。
    并且,加上了目标 clean 用于快速删除指定的文件,因此在后面删除指定文件时只需要使用命令make clean即可。

  • 第四个版本的 Makefile

    OBJS = add.o sub.o test.o
    TARGET = test
    $(TARGET):$(OBJS)
    	gcc -c $^ -o $@
        
    add.o:add.c
    	gcc -c $< -o $@
          
    sub.o:sub.c
        gcc -c $< -o $@
     	
    test.o:test.c
        gcc -c $< -o $@
        
    clean:
    	rm -rf $(OBJS) $(TARGET)
    

    引入了3个自动变量。但其缺点是有命令重复了很多次。

  • 第五个版本的 Makefile

    OBJS = add.o sub.o test.o
    TARGET = test
    $(TARGET):$(OBJS)
    	gcc -c $^ -o $@
        
    # 模式规则
    %.o:%.c
    	gcc -c $< -o $@
    
    clean:
    	rm -rf $(OBJS) $(TARGET)
    

    引入了模式规则,使 Makefile 更精简。

  • 第六个版本的 Makefile

    # 获取当前目录下所有的 .c 文件,并赋值给SRC
    SRC=$(wildcard ./*.c)
    # 将SRC变量中所有出现 .c 的地方替换为 .o
    OBJS=$(patsubst %c, %.o, $(SRC))
    TARGET = test
    $(TARGET):$(OBJS)
    	gcc -c $^ -o $@
        
    # 模式规则
    %.o:%.c
    	gcc -c $< -o $@
    
    clean:
    	rm -rf $(OBJS) $(TARGET)
    

    引入两个函数,自动获取指定目录中所有的 .c 文件。

  • 第七个版本的 Makefile

    # 获取当前目录下所有的 .c 文件,并赋值给SRC
    SRC=$(wildcard ./*.c)
    # 将SRC变量中所有出现 .c 的地方替换为 .o
    OBJS=$(patsubst %c, %.o, $(SRC))
    TARGET = test
    $(TARGET):$(OBJS)
    	gcc -c $^ -o $@
        
    # 模式规则
    %.o:%.c
    	gcc -c $< -o $@
    
    # 声明 clean 为伪目标,
    # 伪目标不去判断目标文件是否存在或者已经更新,
    # 无条件执行命令
    .PHONY:clean
    clean:
    	rm -rf $(OBJS) $(TARGET)
    

Makefile 工作原理

若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来 生成该依赖文件。
image-1655780172833

检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新。
image-1655780208299

0

评论区