前面我们学习了Linux系统的命令行和权限的基础学习。接下来我们将对于Linux系统中内置的开发工具进行学习。只有学习了这些工具才能方便后面利用Linux系统进行开发
作者的个人gitee:楼田莉子 (riko-lou-tian) - Gitee.com喜欢请支持一下
目录
[正常/普通/命令模式(Normal mode)](#正常/普通/命令模式(Normal mode))
[插⼊模式(Insert mode)](#插⼊模式(Insert mode))
[末⾏模式(last line mode)](#末⾏模式(last line mode))
[gcc VS g++](#gcc VS g++)
[CentOS(包括 RHEL、Fedora 等基于 RPM 的系统)](#CentOS(包括 RHEL、Fedora 等基于 RPM 的系统))
[Ubuntu(包括 Debian、Linux Mint 等基于 APT 的系统)](#Ubuntu(包括 Debian、Linux Mint 等基于 APT 的系统))
[git add](#git add)
[git commit](#git commit)
[git push](#git push)
[GDB (GNU Debugger) 常用指令参考表](#GDB (GNU Debugger) 常用指令参考表)
[set var确定问题原因](#set var确定问题原因)
软件包管理器
什么是包管理器
Linux系统下下载软件的常见方式:
1、源代码安装(麻烦且很少见)。
2、rpm包安装(麻烦)。
3、包管理器安装。
软件包和软件包管理器, 就好⽐ "App" 和 "应⽤商店" 这样的关系.
包管理器目前主要有两个:
yum(Yellow dog Updater, Modified)是Linux下⾮常常⽤的⼀种包管理器. 主要应⽤在Fedora,
RedHat, Centos等发⾏版上.(Centos目前已经停更了)
apt:Linux系统的Ubuntu版本主要使⽤apt(Advanced Package Tool)作为其包管理器。apt同样提供了⾃动解决依赖关系、下载和安装软件包的功能
Linux的生态
如何评价一款操作系统的好坏?操作系统被设计出来后最重要的是什么?
必须被更多的人使用,进而形成圈子(是手段不是目的)
本质而言,就是操作系统的生态是否强大

Linux系统云服务器整个流程。
那么我们下载软件的时候从哪里取软件的地址呢?从云服务器内部配置文件,配置了包服务器的url(统一资源定位符)。
在Linux系统下,我们的软件依赖于以下部分。
我们的软件依赖的是别人的库(C语言库和C++库)。但是实际上依赖库也是有以来关系的,如下图所示

这些依赖的库在Linux系统下需要自己去配置安装。
而在Windows系统中,之所以下载这么方便,是因为将这些库全部打包下载的时候自动配置到系统对于的路径之中。
而下载软件是需要提权的(sudo指令)
前面我们知道一款操作系统的好坏取决于其应用人群。但是因为"墙"我们没有办法直接只用一些国外的软件包。因此国内的部分厂商将国外的镜像源替换为国内的镜像源。
有了国内镜像网站后,Linux系统下载软件就变成了这样。

以下是⼀些国内Linux软件安装源的官⽅链接
1、阿⾥云官⽅镜像站
官⽅链接:https://developer.aliyun.com/mirror/
阿⾥云提供了丰富的Linux发⾏版镜像,包括CentOS、Ubuntu、Debian等,⽤⼾可以通过该镜像站快速下载和更新软件包。
2、清华⼤学开源软件镜像站
官⽅链接:https://mirrors.tuna.tsinghua.edu.cn/
清华⼤学镜像站提供了多种Linux发⾏版的镜像,以及Python、Perl、Ruby等编程语⾔的扩展包。该镜像站还提供了丰富的⽂档和教程,帮助⽤⼾更好地使⽤这些软件包。
3、中国科学技术⼤学开源镜像站
官⽅链接:http://mirrors.ustc.edu.cn/
中科⼤镜像站提供了多种Linux发⾏版的镜像,以及常⽤的编程语⾔和开发⼯具。⽤⼾可以通过该镜像站⽅便地获取所需的软件包和⼯具。
4、北京交通⼤学⾃由与开源软件镜像站
官⽅链接:https://mirror.bjtu.edu.cn/
北交⼤镜像站提供了多种Linux发⾏版的镜像,以及相关的软件仓库和⼯具。该镜像站还提供了详细的⽂档和指南,帮助⽤⼾配置和使⽤这些软件源。
5、中国科学院软件研究所镜像站(ISCAS)
官⽅链接:http://mirror.iscas.ac.cn/
ISCAS镜像站提供了多种Linux发⾏版、编程语⾔和开发⼯具的镜像。⽤⼾可以通过该镜像站快速获取所需的软件包和更新。
6、上海交通⼤学开源镜像站
上海交⼤镜像站提供了丰富的Linux软件资源,包括多种发⾏版的镜像和软件仓库。⽤⼾可以通过该镜像站⽅便地下载和安装所需的软件包
7、⽹易开源镜像站
⽹易镜像站提供了多种Linux发⾏版的镜像,以及相关的软件仓库和⼯具。该镜像站还提供了便捷的搜索功能,帮助⽤⼾快速找到所需的软件包
软件源切换:
(1)备份
(2)下载
(3)更新缓存
yum工具操作
查看软件包
通过 yum list 命令可以罗列出当前⼀共有哪些软件包. 由于包的数⽬可能⾮常之多, 这⾥我们需要使⽤ grep 命令只筛选出我们关注的包. 例如:
bash
# Centos
$ yum list | grep lrzsz
lrzsz.x86_64 0.12.20-36.el7 @base
# Ubuntu
$ apt search lrzsz
Sorting... Done
Full Text Search... Done
cutecom/focal 0.30.3-1build1 amd64
Graphical serial terminal, like minicom
lrzsz/focal,now 0.12.21-10 amd64 [installed]
Tools for zmodem/xmodem/ymodem file transfer
$ apt show lrzsz
Package: lrzsz
Version: 0.12.21-10
Priority: optional
Section: universe/comm
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Martin A. Godisch <godisch@debian.org>
注意事项:
软件包名称: 主版本号.次版本号.源程序发⾏号-软件包的发⾏号.主机平台.cpu架构.
"x86_64" 后缀表⽰64位系统的安装包, "i686" 后缀表⽰32位系统安装包. 选择包时要和系统匹配.
"el7" 表⽰操作系统发⾏版的版本. "el7" 表⽰的是 centos7/redhat7. "el6" 表⽰ centos6/redhat6.
最后⼀列, base 表⽰的是 "软件源" 的名称, 类似于 "⼩⽶应⽤商店", "华为应⽤商店" 这样的概念.
Ubuntu 有上述有详细介绍
安装软件
通过 yum, 我们可以通过很简单的⼀条命令完成 gcc 的安装
cpp
# Centos
$ sudo yum install -y lrzsz
# Ubuntu
$ sudo apt install -y lrzsz
yum/apt 会⾃动找到都有哪些软件包需要下载, 这时候敲 "y" 确认安装.
出现 "complete" 字样或者中间未出现报错, 说明安装完成.
注意事项:
安装软件时由于需要向系统⽬录中写⼊内容, ⼀般需要 sudo 或者切到 root 账⼾下才能完成.
yum/apt安装软件只能⼀个装完了再装另⼀个. 正在yum/apt安装⼀个软件的过程中, 如果再尝试⽤yum/apt安装另外⼀个软件, yum/apt会报错.
如果 yum / apt报错, 请⾃⾏查询
卸载软件
也是一条指令
bash
Centos
sudo yum remove [-y] lrzsz
# Ubuntu
sudo apt remove [-y] lrzsz
注意事项
关于 yum / apt 的所有操作必须保证主机(虚拟机)⽹络畅通!!! 可以通过 ping 指令验证
cpp
ping www.baidu.com
# 当天yum / apt也能离线安装,但是和我们当前⽆关,暂不关⼼
如果能三次以上正常响应且没有报错就正常联网
安装源
centos安装源路径:
bash
$ ll /etc/yum.repos.d/
total 16
-rw-r--r-- 1 root root 676 Oct 8 20:47 CentOS-Base.repo # 标准源
-rw-r--r-- 1 root root 230 Aug 27 10:31 epel.repo # 扩展源
# 安装扩展源,⽅便课堂演⽰
# $ sudo yum install -y epel-release
Ubuntu 安装源路径:
bash
$ cat /etc/apt/sources.list # 标准源
$ ll /etc/apt/sources.list.d/ # 扩展源
# ubuntu 安装扩展源,就结合未来具体场景
#测试链接:
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
这里再分享一些有趣的Linux命令:Linux命令行的有趣命令_linux有趣的命令-CSDN博客
编辑器Vim
之前我们学习用的VS2022是在Windows系统下集编写、编译、运行、调试等所有功能为一体的叫做集成开发环境,也就是IDE。
而在Linux系统下开发工具是独立存在的。
编写代码:vim
编译代码:gcc\g++
调试:gbd\cgbd
构建工具:makefile\make\cmake\git
而下面我们先就vim进行详细说明:
vi/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,⽽且还有⼀些新的特性在⾥⾯。例如语法加亮,可视化操作不仅可以在终端运⾏,也可以运⾏于x window、 mac os、 windows。
vi/vim的键盘图

vim模式
vim大约有十二种模式

目前我们先学习vim的3钟常见模式:
命令模式(commandmode)
插⼊模式(Insert mode)
底⾏模式(last line mode)
正常/普通/命令模式(Normal mode)
控制屏幕光标的移动,字符、字或⾏的删除,移动复制某区段及进⼊Insert mode下,或者到 last line mode
插⼊模式(Insert mode)
只有在Insert mode下,才可以做⽂字输⼊,按「ESC」键可回到命令⾏模式。该模式是我们后⾯⽤的最频繁的编辑模式。
末⾏模式(last line mode)
⽂件保存或退出,也可以进⾏⽂件替换,找字符串,列出⾏号等操作。在命令模式下,*shift+:* 即可进⼊该模式。要查看你的所有模式:打开 vim,底⾏模式直接输⼊ :
cpp
help vim-modes
vim的基础操作
进⼊vim,在系统提⽰符号输⼊vim及⽂件名称后,就进⼊vim全屏幕编辑画⾯:
$ vim test.c
不过有⼀点要特别注意,就是你进⼊vim之后,是处于[正常模式],你要切换到[插⼊模式]才能够输⼊⽂字。
[正常模式]切换⾄[插⼊模式]
输⼊a
输⼊i
输⼊o
[插⼊模式]切换⾄[正常模式]
⽬前处于[插⼊模式],就只能⼀直输⼊⽂字,如果发现输错了字,想⽤光标键往回移动,将该字删除,可以先按⼀下「ESC」键转到[正常模式]再删除⽂字。当然,也可以直接删除。
[正常模式]切换⾄[末⾏模式]
「shift + ;」, 其实就是输⼊「:」
退出vim及保存⽂件,在[正常模式]下,按⼀下「:」冒号键进⼊「Last line mode」,例如:
w (保存当前⽂件)
wq (输⼊「wq」,存盘并退出vim)
q! (输⼊q!,不存盘强制退出vim)
vim正常模式命令集
插⼊模式
• 按「i」切换进⼊插⼊模式「insert mode」,按"i"进⼊插⼊模式后是从光标当前位置开始输⼊
⽂件;
• 按「a」进⼊插⼊模式后,是从⽬前光标所在位置的下⼀个位置开始输⼊⽂字;
• 按「o」进⼊插⼊模式后,是插⼊新的⼀⾏,从⾏⾸开始输⼊⽂字。
• 从插⼊模式切换为命令模式
• 按「ESC」键。
移动光标
vim可以直接⽤键盘上的光标来上下左右移动,但正规的vim是⽤⼩写英⽂字⺟「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移⼀格
按「G」:移动到⽂章的最后
按「 $ 」:移动到光标所在⾏的"⾏尾"
按「^」:移动到光标所在⾏的"⾏⾸"
按「w」:光标跳到下个字的开头
按「e」:光标跳到下个字的字尾
按「b」:光标回到上个字的开头
按「#l」:光标移到该⾏的第#个位置,如:5l,56l
按[gg]:进⼊到⽂本开始
按[shift+g]:进⼊⽂本末端。
按"n+shift+g":定位到任意一行。
按「ctrl」+「b」:屏幕往"后"移动⼀⻚
按「ctrl」+「f」:屏幕往"前"移动⼀⻚
按「ctrl」+「u」:屏幕往"后"移动半⻚
按「ctrl」+「d」:屏幕往"前"移动半⻚
删除文字
「x」:每按⼀次,删除光标所在位置的⼀个字符
「#x」:例如,「6x」表⽰删除光标所在位置的"后⾯(包含⾃⼰在内)"6个字符
「X」:⼤写的X,每按⼀次,删除光标所在位置的"前⾯"⼀个字符
「#X」:例如,「20X」表⽰删除光标所在位置的"前⾯"20个字符
「dd」:删除光标所在⾏
「#dd」:从光标所在⾏开始删除#⾏
复制
「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
「#yw」:复制#个字到缓冲区
「yy」:复制光标所在⾏到缓冲区。
「#yy」:例如,「6yy」表⽰拷⻉从光标所在的该⾏"往下数"6⾏⽂字。
「p」:将缓冲区内的字符贴到光标所在位置。注意:所有与"y"有关的复制命令都必须与"p"配合才能完成复制与粘贴功能。(粘贴功能)
替换
「r」:替换光标所在处的字符。
「R」:替换光标所到之处的字符,直到按下「ESC」键为⽌。
撤销上⼀次操作
「u」:如果您误执⾏⼀个命令,可以⻢上按下「u」,回到上⼀个操作。按多次"u"可以执⾏
多次回复。
「ctrl + r」: 撤销的恢复
重复执行命令
! +字符会继续执行最近以字符为开头的历史命令再执行一次
更改
「cw」:更改光标所在处的字到字尾处
「c#w」:例如,「c3w」表⽰更改3个字
跳⾄指定的⾏
「ctrl」+「g」列出光标所在⾏的⾏号。
「#G」:例如,「15G」,表⽰移动光标⾄⽂章的第15⾏⾏⾸。
批量化注释
bash
Ctrl + V # 进入可视块模式
↓/↑ # 用方向键选择多行
I # 进入插入模式
输入注释符 # 如 #、// 等
Esc # 等待 2 秒,所有选中行自动添加注释

批量化取消注释
bash
Ctrl + V # 进入可视块模式
↓/↑ # 选择注释符号区域
d # 删除所有选中字符
匹配关键词搜索
bash
#匹配关键词搜索
:/key+n
vim末行模式命令集
在使⽤末⾏模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进⼊末⾏模式。
列出⾏号
「set nu」: 输⼊「set nu」后,会在⽂件中的每⼀⾏前⾯列出⾏号。
如果要取消掉输入"set nonu"。
强制措施
w!:**"强制"**保存
q!:**"强制"**退出
wq!:**"强制"**保存并退出
主要是权限问题。
如果vim打开文件突然终端退出,vim为了形成临时文件,在当前目录下的一个.swp。临时ls -al
跳到⽂件中的某⼀⾏
「#」:「#」号表⽰⼀个数字,在冒号后输⼊⼀个数字,再按回⻋键就会跳到该⾏了,如输⼊数字15,再回⻋,就会跳到⽂章的第15⾏。
查找字符
「/关键字」: 先按「/」键,再输⼊您想寻找的字符,如果第⼀次找的关键字不是您想要的,可以⼀直按「n」会往后寻找到您要的关键字为⽌。
「?关键字」:先按「?」键,再输⼊您想寻找的字符,如果第⼀次找的关键字不是您想要的,可以⼀直按「n」会往前寻找到您要的关键字为⽌。
问题:∕ 和 ?查找有和区别?
功能 | / (正查) |
? (反查) |
---|---|---|
查找方向 | 向下(文件末尾) | 向上(文件开头) |
n 键 |
跳下一个匹配项 | 跳上一个匹配项 |
N 键 |
跳上一个匹配项 | 跳下一个匹配项 |
起始位置 | 光标处 → 文件尾 | 光标处 → 文件头 |
文本批量化替换
cpp
%s/dst/src/g
#将dst替换为src
vim分屏
通过vim进入相应的.c/.cpp文件后后输入
cpp
:vs [文件名].h
即可实现分屏
可以通过指令ctrl+ww来实现光标切换
保存⽂件
「w」: 在冒号输⼊字⺟「w」就可以将⽂件保存起来
离开vim
「q」:按「q」就是退出,如果⽆法离开vim,可以在「q」后跟⼀个「!」强制离开vim。
「wq」:⼀般建议离开时,搭配「w」⼀起使⽤,这样在退出的时候还可以保存⽂件
shift +zz(ZZ):退出vim
vim操作总结
vim的三种模式:
正常模式
插⼊模式
底⾏模式
vim⼀共有12种总模式,⼤家下来可以研究⼀下
vim操作:打开,关闭,查看,查询,插⼊,删除,替换,撤销,复制等等操作。
接下来来进行测试:
使用vim之前要检查vim是否存在。

随后创建文件test.c(touch指令)。通过这面这行命令进入编写代码:

按下回车后

这种情况下默认无法写入,因此我们要按下i键。

但是vim默认是类似于记事本一样的存在,vim是需要配置的。配置部分后面介绍
然后我们退出。
如果我们复制了3000行代码可以在对应行按yy后然后输入数字+p即可有对应的效果

随后进行注释的时候多行注释可以在crtl+V后直接输入数字+j就向下数行(如上图所示)
接下来进行分屏操作
cpp
vs test.h
随后显示出这样

然后我们可以进行一个简单的多文件代码编写:

接下来我们来编译代码:
vim的简单配置
对vim基本配置,本质是把配置项写入/home/XX/.vimrc配置文件钟
vim的配置只针对当前用户的配置,不影响其他用户
配置文件的位置
在⽬录 /etc/ 下⾯,有个名为vimrc的⽂件,这是系统中公共的vim配置⽂件,对所有⽤⼾都有
效。
⽽在每个⽤⼾的主⽬录下,都可以⾃⼰建⽴私有的配置⽂件,命名为:".vimrc"。例如,/root
⽬录下,通常已经存在⼀个.vimrc⽂件,如果不存在,则创建之。
切换⽤⼾成为⾃⼰执⾏ su ,进⼊⾃⼰的主⼯作⽬录,执⾏ cd ~
打开⾃⼰⽬录下的.vimrc⽂件,执⾏ vim .vimrc
配置项清单及其功能
Linux 系统下 Vim 编辑器配置清单(~/.vimrc
),每个配置项都附带详细的功能说明:
bash
" ================ 基础设置 ================
set nocompatible " 禁用 vi 兼容模式,使用 Vim 的全部功能
filetype on " 启用文件类型检测
filetype plugin on " 加载文件类型插件
filetype indent on " 启用文件类型缩进
syntax on " 启用语法高亮
set encoding=utf-8 " 设置默认编码为 UTF-8
" ================ 显示设置 ================
set number " 显示行号
set relativenumber " 显示相对行号(便于导航)
set cursorline " 高亮当前行
set showcmd " 在右下角显示未完成的命令
set showmatch " 高亮匹配的括号
set title " 在终端标题显示文件名
set laststatus=2 " 总是显示状态栏
set scrolloff=5 " 保持光标距离顶部/底部 5 行
" ================ 编辑体验 ================
set autoindent " 自动缩进
set smartindent " 智能缩进
set expandtab " 将 Tab 转换为空格
set tabstop=4 " Tab 显示为 4 个空格
set shiftwidth=4 " 自动缩进使用 4 个空格
set softtabstop=4 " 退格键删除缩进时一次删除 4 个空格
set backspace=indent,eol,start " 退格键可以跨行删除
set whichwrap+=<,>,h,l " 光标允许在行首/行尾移动到相邻行
" ================ 搜索设置 ================
set incsearch " 输入搜索模式时实时显示匹配
set hlsearch " 高亮所有搜索结果
set ignorecase " 搜索时忽略大小写
set smartcase " 如果搜索包含大写,则区分大小写
" ================ 文件处理 ================
set autoread " 当文件在外部被修改时自动重新加载
set hidden " 允许在未保存时切换缓冲区
set nobackup " 不创建备份文件
set nowritebackup " 编辑时不创建备份
set noswapfile " 禁用交换文件
set undofile " 启用持久撤销历史
set undodir=~/.vim/undo " 设置撤销文件存储目录
" ================ 键位映射 ================
let mapleader = "," " 设置 Leader 键为逗号
" 快速保存退出
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
" 清除搜索高亮
nnoremap <silent> <leader>h :nohlsearch<CR>
" 缓冲区导航
nnoremap <leader>bn :bn<CR> " 下一个缓冲区
nnoremap <leader>bp :bp<CR> " 上一个缓冲区
nnoremap <leader>bd :bd<CR> " 关闭当前缓冲区
" 分屏导航
nnoremap <C-h> <C-w>h " 向左分屏
nnoremap <C-j> <C-w>j " 向下分屏
nnoremap <C-k> <C-w>k " 向上分屏
nnoremap <C-l> <C-w>l " 向右分屏
" 插件管理 (使用 vim-plug)
call plug#begin('~/.vim/plugged')
" 主题插件
Plug 'morhetz/gruvbox' " 受欢迎的暗色主题
Plug 'vim-airline/vim-airline' " 状态栏美化
" 功能增强
Plug 'scrooloose/nerdtree' " 文件浏览器
Plug 'ctrlpvim/ctrlp.vim' " 文件模糊搜索
Plug 'tpope/vim-commentary' " 快速注释/取消注释
Plug 'jiangmiao/auto-pairs' " 自动括号补全
Plug 'neoclide/coc.nvim', {'branch': 'release'} " 智能补全引擎
call plug#end()
" ================ 主题设置 ================
colorscheme gruvbox " 使用 gruvbox 主题
set background=dark " 使用暗色背景
set termguicolors " 启用真彩色支持
" ================ 插件配置 ================
" NERDTree 文件浏览器配置
nnoremap <leader>n :NERDTreeToggle<CR> " 开关文件浏览器
let NERDTreeShowHidden=1 " 显示隐藏文件
" CtrlP 模糊搜索配置
let g:ctrlp_map = '<c-p>' " 快捷键 Ctrl+P
let g:ctrlp_cmd = 'CtrlP'
" Coc.nvim 智能补全配置
inoremap <silent><expr> <TAB>
\ pumvisible() ? "\<C-n>" : "\<TAB>"
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"
" ================ 语言特定设置 ================
" Python 文件设置
autocmd FileType python setlocal
\ shiftwidth=4
\ softtabstop=4
\ tabstop=4
\ textwidth=79
" Markdown 文件设置
autocmd FileType markdown setlocal
\ spell " 启用拼写检查
\ textwidth=80
" ================ 高级功能 ================
" 自动切换工作目录到当前文件目录
autocmd BufEnter * silent! lcd %:p:h
" 保存时自动删除行尾空格
autocmd BufWritePre * %s/\s\+$//e
" 终端真彩色支持
if exists('+termguicolors')
let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
set termguicolors
endif
" ================ 自定义函数 ================
" 切换绝对/相对行号
function! ToggleNumber()
if(&relativenumber == 1)
set norelativenumber
set number
else
set relativenumber
endif
endfunc
nnoremap <leader>tn :call ToggleNumber()<CR>
配置功能分类说明:
类别 | 配置项 | 功能说明 |
---|---|---|
基础设置 | set nocompatible |
使用 Vim 的全部功能而非 vi 兼容模式 |
syntax on |
启用语法高亮 | |
显示优化 | set number |
显示行号 |
set relativenumber |
显示相对行号(便于导航操作) | |
set cursorline |
高亮当前行 | |
编辑体验 | set expandtab |
将 Tab 转换为空格(避免混合缩进) |
set autoindent |
自动继承上一行缩进 | |
set smartindent |
根据语法智能缩进 | |
搜索导航 | set incsearch |
输入搜索内容时实时高亮匹配 |
set hlsearch |
保持搜索结果高亮 | |
set ignorecase |
搜索时忽略大小写 | |
文件处理 | set autoread |
自动重新加载外部修改的文件 |
set undofile |
保存撤销历史,即使关闭文件后重新打开仍可撤销 | |
效率工具 | NERDTree | 侧边栏文件浏览器(,n 切换) |
CtrlP | 文件模糊搜索(Ctrl+P 激活) | |
vim-commentary | 快速注释/取消注释(gcc 注释当前行) |
|
开发支持 | coc.nvim | 智能代码补全(支持多种语言) |
auto-pairs | 自动补全括号、引号等 | |
主题美化 | gruvbox | 专业配色方案(保护眼睛的暗色主题) |
vim-airline | 增强状态栏显示 | |
语言特定 | Python 设置 | 符合 PEP8 的 4 空格缩进 |
Markdown 设置 | 启用拼写检查并设置行宽 | |
高级功能 | 自动切换工作目录 | 始终在当前文件所在目录操作 |
保存时删除行尾空格 | 自动清理多余空格 | |
行号切换功能 | ,tn 切换绝对/相对行号 |
开始之前,我们需要创建一个.vimrc文件

在其中写入配置命令就可以配置好了。
centos7版本可以参考这个文章:
https://gitee.com/HGtz2222/VimForCpp
使用插件
要配置好看的vim,原⽣的配置可能功能不全,可以选择安装插件来完善配置,保证⽤⼾是你要配置的⽤⼾,接下来:
安装TagList插件,下载taglist_xx.zip ,解压完成,将解压出来的doc的内容放到〜/.vim/doc, 将解
压出来的plugin下的内容拷⻉到〜/.vim/plugin
在〜/.vimrc 中添加: let Tlist_Show_One_File=1 letTlist_Exit_OnlyWindow=1 let Tlist_Use_Right_Window=1
安装⽂件浏览器和窗⼝管理器插件: WinManager
下载winmanager.zip,2.X版本以上的
解压winmanager.zip,将解压出来的doc的内容放到〜/.vim/doc, 将解压出来的plugin下的内容拷⻉到〜/.vim/plugin
在〜/.vimrc 中添加 let g:winManagerWindowLayout='FileExplorer|TagListnmap wm :WMToggle<cr>
vim配置文档参考
更具体的内容请看:手把手教你把Vim改装成一个IDE编程环境(图文)_vim 打造成 ide-CSDN博客
其他⼿册,请执⾏ vimtutor 命令
参考资料:
GitHub - wsdjeg/vim-galore-zh_cn: Vim 从入门到精通
gcc/g++编译器
gcc VS g++
gcc编译器是一个C语言编译器,只能编写C语言
g++编译器是一个C/C++编译器
程序的翻译四步骤
-
预处理(进⾏宏替换/去注释/条件编译/头⽂件展开等)
-
编译(⽣成汇编)
-
汇编(⽣成机器可识别代码)
-
连接(⽣成可执⾏⽂件或库⽂件)

gcc编译
格式:
cpp
gcc [选项] 要编译的⽂件 [选项] [⽬标⽂件]
预处理(宏替换)
预处理功能主要包括宏定义,⽂件包含,条件编译,去注释等。
1、头文件展开
2、去注释
3、宏替换
4、条件编译
预处理指令是以#号开头的代码⾏。
实例:
cpp
gcc --E hello.c --o hello.i
选项"-E",该选项的作⽤是让 gcc 在预处理结束后停⽌编译过程。
选项"-S",该选项的作用是编译到汇编语言,生成.s文件
选项"-o"是指⽬标⽂件。如
cpp
gcc test.c -o code.exe
//.exe带不带无所谓
选项"-c"是编译和汇编源文件,生成 .o
目标文件但不链接
选项".i"⽂件为已经过预处理的C原始程序。
条件编译本质是对代码进行裁剪
编译(生成汇编)
在这个阶段中,gcc ⾸先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的⼯作,在检查⽆误后,gcc 把代码翻译成汇编语⾔。
⽤⼾可以使⽤"-S"选项来进⾏查看,该选项只进⾏编译⽽不进⾏汇编,⽣成汇编代码。
实例:
cpp
gcc --S hello.i --o hello.s
汇编(生成机器可识别码)
汇编阶段是把编译阶段⽣成的".s"⽂件转成⽬标⽂件
读者在此可使⽤选项"-c"就可看到汇编代码已转化为".o"的⼆进制⽬标代码了
实例:
bash
gcc --c hello.s --o hello.o
.0二进制文件无法直接运行
连接(生成可执行文件或库文件)
在成功编译之后,就进⼊了链接阶段。
实例:
bash
gcc hello.o --o hello
动态链接和静态链接
在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,但是每个源⽂件都是独⽴编译的,即每个*.c⽂件会形成⼀个*.o⽂件。
为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。这个链接的过程就是静态链接。
静态链接的缺点很明显:
浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本;
更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程序。但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在执⾏的时候运⾏速度快。
动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,序模块都链接成⼀个单独的可执⾏⽂件。
动态链接其实远⽐静态链接要常⽤得多。⽐如我们查看下 hello 这个可执⾏程序依赖的动态库,会发现它就⽤到了⼀个c动态链接库:
bash
$ ldd hello
linux-vdso.so.1 => (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
# ldd命令⽤于打印程序或者库⽂件所依赖的共享库列表。
在这⾥涉及到⼀个重要的概念: 库
我们的C程序中,并没有定义"printf"的函数实现,且在预编译中包含的"stdio.h"中也只有该函数的声明,⽽没有定义函数的实现,那么,是在哪⾥实"printf"函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库⽂件中去了,在没有特别指定,gcc 会到系统默认的搜索路径"/usr/lib"下进⾏查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数"printf"了,⽽这也就是链接的作⽤
静态库与动态库
静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。其后缀名⼀般为".a"
动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。动态库⼀般后缀名为".so",如前⾯所述的libc.so.6 就是动态库。gcc 在编译时默认使⽤动态库。完成了链接之后,gcc 就可以⽣成可执⾏⽂
件,如下所⽰。
但是动态库消失了之后程序就无法运行了
bash
gcc hello.o --o hello
gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证。
注意1:
Linux下,动态库XXX.so, 静态库XXX.a
Windows下,动态库XXX.dll, 静态库XXX.lib
⼀般我们的云服务器,C/C++的静态库并没有安装,可以采⽤如下⽅法安装
bash
# Centos
yum install glibc-static libstdc++-static -y
# Ubuntu
sudo apt update
sudo apt install libc6-dev-static libstdc++6-static -y
动静态库的对比
|------|--------------|-------------------|
| | 动态库 | 静态库 |
| 文件格式 | .so | .a |
| 链接形式 | 动态链接 | 静态链接 |
| 优点 | 节省内存空间 | 不需要库跳转,一旦编译成功不依赖库 |
| 缺点 | 慢,编译后依然依赖动态库 | 可执行程序体积比较大,消耗内存资源 |
Linux和Windows动静态库的区别(了解)
特性 | Linux 系统 | Windows 系统 | 说明 |
---|---|---|---|
静态库扩展名 | .a (Archive) |
.lib (Library) |
Linux 使用 ar 工具打包,Windows 使用 LIB.EXE 创建 |
动态库扩展名 | .so (Shared Object) |
.dll (Dynamic Link Library) |
.dll 是运行时库,配套有导入库 .lib |
导入库 | 不需要专用导入库 | 需要配套的 .lib 导入库 |
Windows 链接 .dll 时必须使用对应的 .lib 导入库 |
文件格式 | ELF (Executable and Linkable Format) | PE/COFF (Portable Executable) | 底层二进制格式不同 |
创建静态库命令 | ar rcs libfoo.a foo.o |
lib /OUT:foo.lib foo.obj |
Linux 用 ar 工具,Windows 用 LIB.EXE |
创建动态库命令 | gcc -shared -o libfoo.so foo.c |
cl /LD foo.c /link /DLL /OUT:foo.dll |
Windows 需额外生成 .exp 和 .lib 文件 |
链接静态库 | gcc main.c -L. -lfoo -o app |
cl main.c foo.lib /Fe:app.exe |
Linux 用 -l 指定库名,Windows 直接指定 .lib 文件 |
链接动态库 | gcc main.c -L. -lfoo -o app |
cl main.c foo.lib /Fe:app.exe |
Linux 链接 .so 与静态库语法相同,Windows 链接时使用导入库 .lib |
运行时加载 | LD_LIBRARY_PATH 或 /etc/ld.so.conf |
系统目录、应用目录或 PATH 环境变量 |
Windows 优先搜索应用所在目录 |
符号解析 | 延迟绑定 (PLT/GOT) | 导入地址表 (IAT) | Linux 使用过程链接表,Windows 使用导入地址表 |
版本控制 | .so 文件带版本号 (如 libfoo.so.1.2 ) |
无原生版本控制 | Linux 通过符号链接管理版本 (libfoo.so -> libfoo.so.1.2 ) |
依赖查看工具 | ldd 、objdump -p |
dumpbin /dependents |
Linux 常用 ldd,Windows 使用 Visual Studio 的 dumpbin |
动态加载API | dlopen() 、dlsym() 、dlclose() |
LoadLibrary() 、GetProcAddress() 、FreeLibrary() |
函数功能类似但接口不同 |
默认链接方式 | 优先动态链接 | 优先静态链接 | Linux 默认优先查找 .so ,Windows 默认优先链接静态库 |
位置无关代码 | -fPIC 编译选项必须 |
默认支持 (DLL 代码天然位置无关) | Windows DLL 不需要特殊编译选项 |
库搜索路径 | /usr/lib 、/usr/local/lib 、LD_LIBRARY_PATH |
%SystemRoot%\System32 、应用目录、PATH |
Linux 可通过 ldconfig 更新缓存 |
符号可见性 | 默认隐藏,需 __attribute__((visibility("default"))) |
默认导出,需 __declspec(dllexport) 显式导出 |
Windows 需要显式标记导出符号 |
依赖冲突 | 符号版本控制 (.symver ) |
无原生解决方案 | Linux 可通过符号版本化解决不同版本库的兼容问题 |
热更新能力 | 支持运行时替换 .so 文件 |
需特殊处理 (DLL 被占用时无法替换) | Linux 的 dlclose() 后可以重新加载 |
调试信息 | 通常分离到 .debug 文件 |
集成在 .pdb (Program Database) 文件 |
Windows 使用单独的 PDB 文件存储调试符号 |
开发流程对比:

gcc的其他选项
类别 | 选项 | 功能说明 |
---|---|---|
基础控制 | -E |
仅执行预处理,输出到标准输出 |
-S |
编译到汇编语言,生成 .s 文件 |
|
-c |
编译和汇编源文件,生成 .o 目标文件但不链接 |
|
-o <file> |
指定输出文件名 | |
-x <language> |
指定输入文件语言(如 c , c++ , assembler ) |
|
-v |
显示详细的编译过程信息 | |
优化选项 | -O0 |
禁用优化(默认) |
-O1 / -O |
基础优化,减少代码大小和执行时间 | |
-O2 |
更高级优化,包括处理器指令调度 | |
-O3 |
激进优化,可能增加代码大小 | |
-Os |
优化代码大小 | |
-Ofast |
激进优化,可能违反严格标准 | |
-flto |
启用链接时优化(Link Time Optimization) | |
调试选项 | -g |
生成调试信息(GDB 使用) |
-ggdb |
生成 GDB 专用调试信息 | |
-g3 |
包含额外调试信息(如宏定义) | |
-p / -pg |
生成性能分析支持代码(用于 gprof ) |
|
警告控制 | -Wall |
启用绝大多数常见警告 |
-Wextra |
启用额外警告 | |
-Werror |
将警告视为错误 | |
-w |
禁用所有警告 | |
-Wno-<warning> |
禁用特定警告(如 -Wno-unused-variable ) |
|
-pedantic |
严格遵守 ANSI/ISO 标准 | |
目录选项 | -I<dir> |
添加头文件搜索目录 |
-L<dir> |
添加库文件搜索目录 | |
-l<library> |
链接指定库(如 -lm 链接数学库) |
|
预处理选项 | -D<macro>[=val] |
定义宏(如 -DDEBUG 或 -DVERSION=1.0 ) |
-U<macro> |
取消宏定义 | |
-include <file> |
强制包含头文件 | |
-M |
输出依赖规则(用于 makefile) | |
-MM |
输出依赖规则(排除系统头文件) | |
架构选项 | -m32 |
生成 32 位代码 |
-m64 |
生成 64 位代码 | |
-march=<arch> |
指定目标架构(如 -march=native ) |
|
-mtune=<cpu> |
微调特定 CPU 的性能 | |
代码生成 | -fPIC |
生成位置无关代码(用于共享库) |
-fPIE |
生成位置无关可执行文件 | |
-shared |
生成共享库(.so 文件) | |
-static |
静态链接所有库 | |
-pthread |
添加 POSIX 线程支持 | |
语言标准 | -std=<standard> |
指定语言标准(如 -std=c11 , -std=c++17 ) |
-ansi |
等价于 -std=c90 |
|
诊断选项 | -fdiagnostics-color |
启用彩色错误信息 |
-save-temps |
保存临时文件(.i, .s, .o) | |
-### |
显示但不执行命令 | |
链接控制 | -Wl,<option> |
传递选项给链接器(如 -Wl,-rpath=./ ) |
-nostdlib |
不链接标准库 | |
-nodefaultlibs |
不链接默认系统库 | |
-s |
移除所有符号信息 |
make/Makefile------自动化构建
背景
什么是构建
代码构建(Build)是指将人类可读的源代码(如 .c
, .cpp
, .java
, .py
等)及其相关资源文件(如头文件 .h
,配置文件,图像,库文件等),通过一系列自动化工具和步骤,转换 、组合 和打包 成计算机可以直接执行或部署的目标形式的过程
构建的步骤
预处理、编译、汇编、链接、代码生成、资源处理、代码优化、打包、测试、文档生成
make和makefile的介绍
在VS中,VS自动的帮我们自动构建项目,但是Linux系统下工具是独立的,因此需要单独的工具去构建代码
make是命令,makefile是文件
会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒
⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作
makefile带来的好处就是⸺"⾃动化编译",⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。
make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可⻅,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。
make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。
make和makefile------依赖关系和依赖方法
make本质上是解析makefile上的命令
就像下图所示的就是依赖关系与依赖方法的一种应用

test.exe由test.o产生,这时依赖关系,而想要产生test.exe就需要
gcc test.o-o test.exe
这是依赖方法。
makeflie实际上类似于递归或者栈的类型结构。
接下来我们举一个具体的Makefile写法的例子

其中.PHONY的作用是声明一个符号。在这里声明的就是clean。声明这个符号表明该符号为一个伪目标文件。
细节1:依赖关系必须存在,依赖文件列表可以为空
细节2:依赖方法可以为任何命令
细节3:clean目标只是利用make的自动推导能力让它自动执行对应的命令,构建工程视角看起来就是清理项目,本质就是删除不需要的临时文件
细节4:make命令后面可以跟目标文件名,跟谁的解析谁的依赖关系和依赖方法,,make默认只有一条完整的推导链
.PHONY:用来修饰伪目标。
伪目标:总是被执行的命令
那么为什么.exe不用.PHONY?
是为了加快编译效率。如果多个源文件改正一个而其他不改变只需要改变对应的改变的源文件即可。源文件更改才重新编译
那么.PHONY是如何做到的呢?为什么之前gcc无法二次编译?
本质也是文件,编译也需要时间
下图所示需要stat指令,具体表现如下:
cpp
stat 文件名

那么如何判定源文件是否需要被重新编译?如何判断源文件和可执行程序谁更新?
看修改时间(对应的红框的内容)。同时判断新旧也是依赖于此。
那么.PHONY是怎么做到让gcc和对应的命令总是被执行的?让它们忽略MOD时间对比新旧
通过上面观察,我们将Access、Modify、Change的三个时间统称之为ACM时间。那这两者有什么区别呢?
前面我们知道文件=文件内容+文件属性。当文件内容更改的时候,Modify的时间就会更改;当文件属性更改的时候,Change的时间就会更改。
但是要是改变了文件内容,就改变了文件的大小,包括Mod时间也是属性。
access时间是文件被访问的时间(cat)
stat是查看文件,更新时间,修改了文件属性,进而将其刷新到磁盘上。会增加访问磁盘的次数,导致了OS整体效率变低。因此通常访问多次才改变一次,就是防止效率过低
但是如果我们突然要给文件改名字的话这就非常麻烦了。因此我们可以这样

BIN和SRC自定义文件的名字(不需要类型,均视之为字符串)
$(BIN)是将BIN中的内容提取出来,@会抑制命令的显示只显示结果
@和^中@、^是gcc的内置变量。其中 @的@表示依赖关系中的目标文件,^的^表示依赖关系中的依赖文件列表
编译多个.c文件的时候可以用这种写法

以上部分都是编译C语言的,如果这个makefile想拿到C++也能编译,可以这样更改

注释:
bash
BIN=proc.exe # 定义变量
CC=gcc
#SRC=$(shell ls *.c) # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c) # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名
OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表
LFLAGS=-o # 链接选项
FLAGS=-c # 编译选项
RM=rm -f # 引⼊命令
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^ # $@:代表⽬标⽂件名。 $^: 代表依赖⽂件列表
@echo "linking ... $^ to $@"
%.o:%.c # %.c 展开当前⽬录下所有的.c。 %.o: 同时展开同
名.o
@$(CC) $(FLAGS) $< # %<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。
@echo "compling ... $< to $@" # @:不回显命令
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN) # $(RM): 替换,⽤变量内容替换它
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)
第一个程序尝试------进度条
回车和换行
概念 | 老式打字机 | Windows系统 | Linux系统 |
---|---|---|---|
回车(CR) | 滑架移回行首 | \r (独立操作) |
\r (回到行首) |
换行(LF) | 纸张上移一行 | \n (仅下移行) |
\n (下移+回行首) |
标准换行 | 需CR+LF两个操作 | \r\n 组合 |
\n 单独 |
核心用途 | 物理定位 | 文件行尾标识 | 文件行尾+终端控制 |
现代应用 | 历史遗留 | 文件兼容 | 终端UI控制 |
行缓冲区
当我们写下这段代码的时候,会打印后休息3秒(gcc编译器下,如果是VS下是3秒)
cpp
#include <stdio.h>
int main()
{
printf("hello world!\n");
sleep(3);
return 0;
}
在sleep期间printf函数已经执行完了但是没看到,是因为内容被放到了缓冲区内。

结果为:打印内容后休眠五秒

当变成这样的时候
结果为:休眠了5秒什么都不打印。
原因:标准输出stdout
默认采用行缓冲模式 。行缓冲规则为遇到换行符\n
时自动刷新缓冲区。因此输出:printf("hello Boogiepop\r")
使用的是回车符\r
而非换行符\n
,结果就是:由于未遇到\n
,缓冲区在sleep(5)
期间不会被刷新。\r
的功能是将光标移回行首,但不会清除已输出内容,程序结束后缓冲区被刷新,输出内容会显示,但立即被覆盖。
就像下面这张图:

当为下面这张图的时候,
结果同第一种情况。

结论1:显示器都是行刷新的,只有碰到\r\n或者\n才会刷新显示出来
结论2:程序运行结束缓冲区内部的数据会被自动刷新
缓冲区存在于内存之中。
那么如果我们想不带\r\n或者\n想看到结果如何做呢? 采用一个C语言函数
cpp
int* fflush(FILE*stream)
文件打开的时候默认打开了三个文件流:标准输入(extern FILE* stdin)、标准输出(extern FILE* stdout)、标准错误(extern FILE* stderror)
缓冲区也有归属。
因此我们可以这样写

结果为:打印内容后缓冲5秒

<unistd.h>头文件主要库函数的介绍
- 进程控制函数
函数原型 | 作用描述 | 返回值 |
---|---|---|
pid_t fork(void); |
创建子进程(当前进程的副本) | 子进程返回0,父进程返回子进程ID |
int exec[lv]p[e] (...); |
执行新程序(替换当前进程映像) | 成功无返回,失败返回-1 |
void _exit(int status); |
立即终止进程(不执行清理) | 无返回 |
pid_t getpid(void); |
获取当前进程ID | 进程ID |
pid_t getppid(void); |
获取父进程ID | 父进程ID |
示例:创建子进程
cpp
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid = fork();
if (pid == 0)
{
printf("Child process: PID=%d\n", getpid());
}
else if (pid > 0)
{
printf("Parent process: PID=%d, Child PID=%d\n", getpid(), pid);
}
else
{
perror("fork failed");
}
return 0;
}
- 文件操作函数
函数原型 | 作用描述 | 返回值 |
---|---|---|
int access(const char *path, int mode); |
检查文件访问权限 | 0=存在,-1=错误 |
int unlink(const char *path); |
删除文件(目录项) | 0=成功,-1=错误 |
int rmdir(const char *path); |
删除空目录 | 0=成功,-1=错误 |
int close(int fd); |
关闭文件描述符 | 0=成功,-1=错误 |
off_t lseek(int fd, off_t offset, int whence); |
移动文件读写位置 | 新位置,-1=错误 |
文件权限检查
cpp
#include <unistd.h>
#include <stdio.h>
int main()
{
const char *file = "/etc/passwd";
if (access(file, F_OK) == 0)
printf("%s exists\n", file);
if (access(file, R_OK) == 0)
printf("%s is readable\n", file);
return 0;
}
- 文件描述符操作
函数原型 | 作用描述 | 返回值 |
---|---|---|
int dup(int oldfd); |
复制文件描述符 | 新描述符,-1=错误 |
int dup2(int oldfd, int newfd); |
复制到指定描述符 | 新描述符,-1=错误 |
int pipe(int pipefd[2]); |
创建管道(进程间通信) | 0=成功,-1=错误 |
示例:管道通信
cpp
#include <unistd.h>
#include <stdio.h>
int main()
{
int pipefd[2];
char buf[20];
pipe(pipefd); // 创建管道
if (fork() == 0) // 子进程
{
close(pipefd[0]); // 关闭读端
write(pipefd[1], "Hello", 6);
close(pipefd[1]);
}
else // 父进程
{
close(pipefd[1]); // 关闭写端
read(pipefd[0], buf, sizeof(buf));
printf("Received: %s\n", buf);
close(pipefd[0]);
}
return 0;
}
- 系统配置与限制
函数原型 | 作用描述 | 返回值 |
---|---|---|
long sysconf(int name); |
获取系统运行时限制值 | 值,-1=错误 |
char *getcwd(char *buf, size_t size); |
获取当前工作目录 | buf地址,NULL=错误 |
int chdir(const char *path); |
改变当前工作目录 | 0=成功,-1=错误 |
示例:获取系统限制
cpp
#include <unistd.h>
#include <stdio.h>
int main()
{
printf("Max open files: %ld\n", sysconf(_SC_OPEN_MAX));
printf("Page size: %ld bytes\n", sysconf(_SC_PAGESIZE));
char cwd[1024];
if (getcwd(cwd, sizeof(cwd))
{
printf("Current directory: %s\n", cwd);
}
return 0;
}
- 用户与组管理
函数原型 | 作用描述 | 返回值 |
---|---|---|
uid_t getuid(void); |
获取真实用户ID | 用户ID |
gid_t getgid(void); |
获取真实组ID | 组ID |
int setuid(uid_t uid); |
设置用户ID | 0=成功,-1=错误 |
int setgid(gid_t gid); |
设置组ID | 0=成功,-1=错误 |
- 时间与延迟
函数原型 | 作用描述 | 返回值 |
---|---|---|
unsigned int sleep(unsigned int seconds); |
睡眠指定秒数 | 剩余秒数 |
int usleep(useconds_t usec); |
微秒级睡眠 | 0=成功,-1=错误 |
int pause(void); |
挂起进程直到收到信号 | 总是返回-1 |
示例:精确延时
cpp
#include <unistd.h>
#include <stdio.h>
#include <time.h>
int main()
{
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
// 休眠250毫秒
usleep(250 * 1000);
clock_gettime(CLOCK_MONOTONIC, &end);
long elapsed = (end.tv_sec - start.tv_sec) * 1000000 +
(end.tv_nsec - start.tv_nsec) / 1000;
printf("Actual sleep: %ld microseconds\n", elapsed);
return 0;
}
倒计时代码
cpp
#include<stdio.h>
#include<unistd.h>//Linux系统下专属头文件
int main()
{
int cnt = 9;
while (cnt >= 0)
{
printf("%d\r", cnt);//\r将光标回到最开始
fflush(stdout);
sleep(1);
cnt--;
}
printf("\n");
// printf("hello Boogiepop");
// fflush(stdout);
// sleep(5);
return 0;
}
显示器显示的都是字符,所以才需要ASCII码表
cpp
整数------>printf格式化------>字符打印
进度条代码
process.h
cpp
#pragma once
#include<stdio.h>
#include<unistd.h>
//verson1
void process();
//具体应用场景
void flashprocess(double total,double cur);
main.c
cpp
int main()
{
//process();
printf("download:1000MB:\n");
download(1000, flashprocess);
printf("download:20MB:\n");
download(20, flashprocess);
printf("download:1GB:\n");
download(1024, flashprocess);
printf("download:56MB:\n");
download(56, flashprocess);
return 0;
}
version1
process.c
cpp
//version 1
//仅仅是教学原理,不作为实际应用使用
void process()
{//光标优化
const char* str="|/-\\";
int len=strlen(str);
//字符串
char processbuffer[size];
memset(processbuffer,'\0',sizeof(processbuffer));
int cnt=0;
while(cnt<=100)
{// 打印字符 打印数字% 打印光标
printf("[%-100s][%d%%][%c]\r",processbuffer,cnt,str[cnt%len]);
//%%
//单个 % 在 printf 中是格式化占位符的起始符号(如 %d, %s)。
//当需要直接输出百分号字符 %(而非作为占位符)时,必须使用 %% 来转义。
//这里 %% 会被输出为一个字面的 % 字符。
fflush(stdout);
processbuffer[cnt++]=style;
//sleep(1);每隔一秒打印一次
usleep(50000);//usleep单位是毫秒,表示限定5秒内打印完
}
printf("\n");
}
version2
cpp
//version2:耦合性太强,不适合实际应用
//网络浮动
// 基本网速 浮动网速
double speedfloat(double start,double range)//示例(1.0,3.0)->[1.0,4.0]
{
int int_range=(int)range;
return start + rand()%int_range + (range + int_range);
}
void download(int total)
{
srand(time(NULL));
double cur=0.0;
while(1)//(cur<=total)
{
if(cur>total)
{
cur=total;//模拟下载完成
flashprocess(total,cur);//更新进度:按照下载进度更新进度
break;
}
flashprocess(total,cur);//更新进度:按照下载进度更新进度
//cur+=speed;//模拟下载行为
cur+=speedfloat(speed,30.3);
usleep(30000);
}
}
version3
cpp
//version3
//相较于version2,这也是一种特殊的代码
//实际业务的时候,这种方法可以复用,代码维护性较好
//函数指针
typedef void (*callback_t)(double, double);
//网络浮动
// 基本网速 浮动网速
double speedfloat(double start, double range)//示例(1.0,3.0)->[1.0,4.0]
{
int int_range = (int)range;
return start + rand() % int_range + (range + int_range);
}
//cb:回调函数
void download(int total, callback_t cd)
{
srand(time(NULL));
double cur = 0.0;
while (1)//(cur<=total)
{
if (cur > total)
{
cur = total;//模拟下载完成
cd(total, cur);//更新进度:按照下载进度更新进度
break;
}
cd(total, cur);//更新进度:按照下载进度更新进度
//cur+=speed;//模拟下载行为
cur += speedfloat(speed, 30.3);
Sleep(300);//模拟网络延迟
//如果在Linux系统下,可以使用usleep代替Sleep,单位为微秒
}
}
版本控制器------git
不知道你⼯作或学习时,有没有遇到这样的情况:我们在编写各种⽂档时,为了防⽌⽂档丢失,更改失误,失误后能恢复到原来的版本,不得不复制出⼀个副本,⽐如:
"报告-v1" "报告-v2" "报告-v3" "报告-确定版" "报告-最终版"等等
每个版本有各⾃的内容,但最终会只有⼀份报告需要被我们使⽤ 。
但在此之前的⼯作都需要这些不同版本的报告,于是每次都是复制粘贴副本,产出的⽂件就越来越多,⽂件多不是问题,问题是:随着版本数量的不断增多,你还记得这些版本各⾃都是修改了什么吗?
⽂档如此,我们写的项⽬代码,也是存在这个问题的!!
版本控制器
为了能够更⽅便我们管理这些不同版本的⽂件,便有了版本控制器。所谓的版本控制器,就是能让你了解到⼀个⽂件的历史,以及它的发展过程的系统。通俗的讲就是⼀个可以记录⼯程的每⼀次改动和版本迭代的⼀个管理系统,同时也⽅便多⼈协同作业。
⽬前最主流的版本控制器就是 Git 。Git 可以控制电脑上所有格式的⽂件,例如 doc、excel、dwg、dgn、rvt等等。对于我们开发⼈员来说,Git 最重要的就是可以帮助我们管理软件开发项⽬中的源代码⽂件!
git简史
同⽣活中的许多伟⼤事物⼀样,Git 诞⽣于⼀个极富纷争⼤举创新的年代。
Linux 内核开源项⽬有着为数众多的参与者。 绝⼤多数的 Linux 内核维护⼯作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。 到 2002 年,整个项⽬组开始启⽤⼀个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。
到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使⽤ BitKeeper 的权⼒。 这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使⽤ BitKeeper 时的经验教训,开发出⾃⼰的版本系统。 他们对新的系统制订了若⼲⽬标:
• 速度
• 简单的设计
• 对⾮线性开发模式的强⼒⽀持(允许成千上万个并⾏开发的分⽀)
• 完全分布式
• 有能⼒⾼效管理类似 Linux 内核⼀样的超⼤规模项⽬(速度和数据量)
⾃诞⽣于 2005 年以来,Git ⽇臻成熟完善,在⾼度易⽤的同时,仍然保留着初期设定的⽬标。 它的速度⻜快,极其适合管理⼤项⽬,有着令⼈难以置信的⾮线性分⽀管理系统。
结论1:git进行控制的时候是同步记录变化来进行控制的。
结论2:git是去中心化的、分布式的版本控制器。
安装
CentOS(包括 RHEL、Fedora 等基于 RPM 的系统)
使用 yum(CentOS 7 及之前版本)
bash
# 更新包索引
sudo yum update
# 安装 Git
sudo yum install git
# 验证安装
git --version
使用 dnf(CentOS 8 及之后版本)
bash
# 更新包索引
sudo dnf update
# 安装 Git
sudo dnf install git
# 验证安装
git --version
从源码安装(获取最新版本)
bash
# 安装依赖
sudo yum groupinstall "Development Tools"
sudo yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel
# 下载源码
cd /tmp
curl -O https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.41.0.tar.gz
tar -xf git-*.tar.gz
cd git-*
# 编译安装
./configure --prefix=/usr/local
make
sudo make install
# 验证安装
/usr/local/bin/git --version
Ubuntu(包括 Debian、Linux Mint 等基于 APT 的系统)
使用 apt(推荐)
bash
# 更新包索引
sudo apt update
# 安装 Git
sudo apt install git
# 验证安装
git --version
通过 PPA 安装(获取最新版本)
bash
# 添加 PPA 仓库
sudo add-apt-repository ppa:git-core/ppa
sudo apt update
# 安装 Git
sudo apt install git
# 验证安装
git --version
从源码安装
bash
# 安装依赖
sudo apt update
sudo apt install make libssl-dev libghc-zlib-dev libcurl4-gnutls-dev libexpat-dev gettext unzip
# 下载源码
cd /tmp
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.41.0.tar.gz
tar -xf git-*.tar.gz
cd git-*
# 编译安装
make prefix=/usr/local all
sudo make prefix=/usr/local install
# 验证安装
git --version
通用配置(安装后建议设置)
bash
# 设置用户名
git config --global user.name "Your Name"
# 设置邮箱
git config --global user.email "youremail@example.com"
# 设置默认编辑器(可选)
git config --global core.editor "vim"
# 查看配置
git config --list
版本对照表
系统 | 默认仓库版本 | 最新版本(2023年7月) |
---|---|---|
CentOS 7 | ~1.8.3 | 2.41.0 |
CentOS 8 | ~2.18.2 | 2.41.0 |
Ubuntu 20.04 | ~2.25.1 | 2.41.0 |
Ubuntu 22.04 | ~2.34.1 | 2.41.0 |
选择建议:
-
普通用户 :使用默认仓库安装(
yum install git
或apt install git
) -
开发者:通过 PPA(Ubuntu)或源码安装获取最新功能
-
企业环境:使用默认仓库版本确保稳定性
注意:从源码安装需要编译工具链(gcc、make等),安装时间较长但能获得最新功能和性能优化。
Github&&gitee
Github和gitee是git的两个分支。相较于 Github,gitee作为国内的git虽然在数量上不如 Github,但是门槛更低,所以推荐小白使用gitee。
git是一个去中心化的版本控制器+网络通信

使用gitee需要事先注册账号登录并创建克隆出本地仓库。这里不过多展示了,可以参考这个视频:【gitee(码云)的注册和代码提交【手把手】】https://www.bilibili.com/video/BV1hf4y1W7yT?vd_source=5e6b89a78dbb4e3194b300fc043515c4
UP主:鹏哥C语言
git的三板斧
git add
将代码放到刚才下载好的⽬录中
bash
git add [⽂件名]
将需要⽤ git 管理的⽂件告知 git
也可以写作
bash
git add .
所有修改的文件全部提交

git commit
提交改动到本地
bash
git commit -m "提交日志"
提交的时候应该注明提交⽇志, 描述改动的详细内容.
git push
完成上面两步后我们完成了本地的git操作,接下来我们要将代码推送到远程仓库
同步到远端服务器上
bash
git push
需要填⼊⽤⼾名密码. 同步成功后, 刷新 Github ⻚⾯就能看到代码改动了
git免密码pull和push:git本地免密码和账号pull、push_没有git账号怎么拉代码-CSDN博客
其他
git log
作用 :查看项目提交历史记录
关键功能:
-
显示所有提交的完整历史(按时间倒序)
-
查看每次提交的:作者、时间、提交ID、提交信息
-
支持多种显示格式(简洁模式、图形化分支等)
bash# 基础用法 git log # 简洁单行显示 git log --oneline # 图形化显示分支结构 git log --graph --all # 查看特定文件的修改历史 git log -p filename
git clone
-
将远程仓库完整复制到本地
基础语法:
bash
git clone <仓库URL> [本地目录名]
主要选项应用:
bash
# 1. HTTPS 克隆(需输入账号密码)
git clone https://gitee.com/user/project.git
# 2. SSH 克隆(需配置密钥)
git clone git@gitee.com:user/project.git
# 3. 克隆到指定目录
git clone https://url/project.git my-project
# 4. 克隆特定分支
git clone -b develop https://url/project.git
# 5. 浅克隆(只获取最近提交)
git clone --depth 1 https://url/project.git
git status
作用 :查看工作目录和暂存区的当前状态
关键功能:
-
显示已修改但未暂存的文件(红色)
-
显示已暂存待提交的文件(绿色)
-
显示未跟踪的新文件
-
显示当前所在分支信息
bash# 基础用法 git status # 简洁模式显示 git status -s # 输出示例: # M modified_file (已修改未暂存) # A new_file (已暂存新文件) # ?? untracked_file (未跟踪文件)
git pull
作用 :从远程仓库获取最新代码并合并到本地分支
关键功能:
-
相当于
git fetch
+git merge
的组合操作 -
同步远程仓库的最新变更到本地
-
自动尝试合并远程分支到当前分支
bash# 基础用法(默认拉取当前分支对应的远程分支) git pull # 拉取特定远程分支并合并 git pull origin feature-branch # 使用 rebase 代替 merge(保持提交历史线性) git pull --rebase
应用git开发的过程中,你提交你自己的代码必须把别人的代码pull下来。
本质:调用你的文本编辑器、vim同时呈现出来。让用户自己去解决问腿。
.gitignore
文件解析
作用 :指定需要被 Git 忽略的文件和目录
核心功能:
-
防止特定文件被纳入版本控制
-
避免提交临时文件、编译产物、敏感信息等
-
支持通配符和目录匹配
bash# 忽略所有 .log 文件 *.log # 忽略 build 目录 build/ # 忽略特定文件 config.ini # 不忽略重要的 .log 文件 !important.log # 忽略所有目录下的 node_modules **/node_modules/
配置要点:
-
文件应放在项目根目录,命名为
.gitignore
-
支持使用
#
添加注释 -
支持使用
!
取反规则 -
目录使用
/
结尾(如dist/
)
示例:
bash
# 1. 检查当前状态
git status
# 2. 拉取远程最新代码(避免冲突)
git pull --rebase
# 3. 添加修改文件到暂存区
git add .
# 4. 提交变更
git commit -m "更新功能"
# 5. 查看提交历史确认
git log --oneline -n 3
# 6. 推送变更到远程
git push
gbd/cgbd------调试器
示例:
cpp
// mycmd.c
#include <stdio.h>
int Sum(int s, int e)
{
int result = 0;
for (int i = s; i <= e; i++)
{
result += i;
}
return result;
}
int main()
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
printf("running done, result is: [%d-%d]=%d\n", start, end, n);
return 0;
}
预备
程序的发布⽅式有两种, debug 模式和 release 模式, 在Linux系统下gcc/g++ 出来的⼆进制程序,默认是 release 模式。
要使⽤gdb调试,必须在源代码⽣成⼆进制程序的时候, 加上 -g 选项,如果没有添加,程序⽆法被编译
bash
$ gcc mycmd.c -o mycmd # 默认模式,不⽀持调试
$ file mycmd
mycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically
linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=82f5cbaada10a9987d9f325384861a88d278b160, for GNU/Linux
3.2.0, not stripped
$ gcc mycmd.c -o mycmd -g # debug模式
$ file mycmd
mycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically
linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=3d5a2317809ef86c7827e9199cfefa622e3c187f, for GNU/Linux
3.2.0, with debug_info, not stripped
调试的本质:
当程序发生了问题的时候:发现问题、找到问题、解决问题。
gdb是找到问题、支持用户分析解决问题的工具
gbd常见的指令
GDB (GNU Debugger) 常用指令参考表
开始: gdb binFile
退出: ctrl + d 或 quit 调试命令
命令 | 作用 | 样例 |
---|---|---|
list/l | 显示源代码,从上次位置开始,每次列出 10 行 | list/l 10 |
list/l 函数名 | 列出指定函数的源代码 | list/l main |
list/l ⽂件名:⾏号 | 列出指定⽂件的源代码 | list/l mycmd.c:1 |
r/run | 从程序开始连续执⾏ | run |
n/next | 单步执⾏,不进⼊函数内部 | next |
s/step | 单步执⾏,进⼊函数内部 | step |
break/b [⽂件名:] ⾏号 | 在指定⾏号设置断点 | break 10 break test.c:10 |
break/b 函数名 | 在函数开头设置断点 | break main |
info break/b | 查看当前所有断点的信息 | info break |
finish | 执⾏到当前函数返回,然后停⽌ | finish |
print/p 表达式 | 打印表达式的值 | print start+end |
p 变量 | 打印指定变量的值 | p x |
set var 变量 = 值 | 修改变量的值 | set var i=10 |
continue/c | 从当前位置开始连续执⾏程序 | continue |
delete/d breakpoints | 删除所有断点 | delete breakpoints |
delete/d breakpoints n | 删除序号为 n 的断点 | delete breakpoints 1 |
disable breakpoints | 禁⽤所有断点 | disable breakpoints |
enable breakpoints | 启⽤所有断点 | enable breakpoints |
info/i breakpoints | 查看当前设置的断点列表 | info breakpoints |
display 变量名 | 跟踪显⽰指定变量的值(每次停⽌时) | display x |
undisplay 编号 | 取消对指定编号的变量的跟踪显⽰ | undisplay 1 |
until X ⾏号 | 执⾏到指定⾏号 | until 20 |
backtrace/bt | 查看当前执⾏栈的各级函数调⽤及参数 | backtrace |
info/i locals | 查看当前栈帧的局部变量值 | info locals |
quit | 退出 GDB 调试器 | quit |
x命令格式:
bash
x/[数量][格式][单位] <地址>
-
数量:显示的数据项数
-
格式:
-
x
:十六进制 -
d
:十进制 -
u
:无符号十进制 -
o
:八进制 -
t
:二进制 -
a
:地址 -
c
:字符 -
f
:浮点数
-
-
单位:
-
b
:字节(byte) -
h
:半字(2字节) -
w
:字(4字节) -
g
:双字(8字节)
-
示例:
bash
# 1. 编译带调试信息的程序
gcc -g -o myapp main.c
# 2. 启动GDB
gdb ./myapp
# 3. 在main函数设置断点
(gdb) b main
# 4. 运行程序
(gdb) run
# 5. 单步执行
(gdb) n
# 6. 查看变量
(gdb) p count
# 7. 继续执行
(gdb) c
# 8. 退出
(gdb) q
安装cgbd
虽然前面我们介绍了很多gbd的内容,但是gbd相比来说比较难用。实际上我们更推荐使用cgbd.虽然是⿊屏,但是还是想看到代码调试
推荐安装cgdb:
bash
Ubuntu: sudo apt-get install -y cgdb
Centos: sudo yum install -y cgdb
打开之后界面是这个样子的。
常用技巧
watch
执⾏时监视⼀个表达式(如变量)的值。如果监视的表达式在程序运⾏期间的值发⽣变化,GDB 会暂停程序的执⾏,并通知使⽤者
cpp
gdb) l main
11
12 return result;
13 }
14
15 int main()
16 {
17 int start = 1;
18 int end = 100;
19 printf("I will begin\n");
20 int n = Sum(start, end);
(gdb) b 20
Breakpoint 1 at 0x11c3: file mycmd.c, line 20.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000011c3 in main at mycmd.c:20
(gdb) r
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:20
20 int n = Sum(start, end);
(gdb) s
Sum (s=32767, e=-7136) at mycmd.c:5
5 {
(gdb) n
6 int result = 0;
(gdb) watch result
Hardware watchpoint 2: result
(gdb) c
Continuing.
Hardware watchpoint 2: result
Old value = -6896
New value = 0
Sum (s=1, e=100) at mycmd.c:7
7 for(int i = s; i <= e; i++)
(gdb) c
Continuing.
Hardware watchpoint 2: result
Old value = 0
New value = 1
Sum (s=1, e=100) at mycmd.c:7
7 for(int i = s; i <= e; i++)
(gdb) c
Continuing.
Hardware watchpoint 2: result
Old value = 1
New value = 3
Sum (s=1, e=100) at mycmd.c:7
7 for(int i = s; i <= e; i++)
(gdb) c
Continuing.
Hardware watchpoint 2: result
Old value = 3
New value = 6
Sum (s=1, e=100) at mycmd.c:7
7 for(int i = s; i <= e; i++)
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
2 hw watchpoint keep y result
breakpoint already hit 4 times
(gdb) d 2
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
(gdb) finish
Run till exit from #0 Sum (s=1, e=100) at mycmd.c:7
0x00005555555551d2 in main () at mycmd.c:20
20 int n = Sum(start, end);
Value returned is $1 = 5050
注意:如果你有⼀些变量不应该修改,但是你怀疑它修改导致了问题,你可以watch它,如果变化了,就会通知你.
set var确定问题原因
更改⼀下标志位,假设我们想得到
cpp
+-result
cpp
// mycmd.c
#include <stdio.h>
int flag = 0; // 故意错误
//int flag = -1;
//int flag = 1;
int Sum(int s, int e)
{
int result = 0;
for(int i = s; i <= e; i++)
{
result += i;
}
return result*flag;
}
int main()
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
printf("running done, result is: [%d-%d]=%d\n", start, end, n);
return 0;
}
(gdb) l main
15
16 return result*flag;
17 }
18
19 int main()
20 {
21 int start = 1;
22 int end = 100;
23 printf("I will begin\n");
24 int n = Sum(start, end);
(gdb) b 24
Breakpoint 1 at 0x11ca: file mycmd.c, line 24.
(gdb) r
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:24
24 int n = Sum(start, end);
(gdb) n
25 printf("running done, result is: [%d-%d]=%d\n", start, end,
n);
(gdb) n
running done, result is: [1-100]=0 # 这⾥结果为什么是0?
26 return 0;
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:24
24 int n = Sum(start, end);
(gdb) s
Sum (s=32767, e=-7136) at mycmd.c:9
9 {
(gdb) n
10 int result = 0;
(gdb) n
11 for(int i = s; i <= e; i++)
(gdb)
13 result += i;
(gdb)
11 for(int i = s; i <= e; i++)
(gdb)
13 result += i;
(gdb) until 14
Sum (s=1, e=100) at mycmd.c:16
16 return result*flag;
(gdb) p result
$1 = 5050
(gdb) p flag
$2 = 0
(gdb) set var flag=1 # 更改flag的值,确认是否是它的原因
(gdb) p flag
$3 = 1
(gdb) n
17 }
(gdb) n
main () at mycmd.c:25
25 printf("running done, result is: [%d-%d]=%d\n", start, end,
n);
(gdb) n
running done, result is: [1-100]=5050 # 是它的原因
26 return 0;
条件断点
添加条件断点
cpp
(gdb) l main
11
12 return result;
13 }
14
15 int main()
16 {
17 int start = 1;
18 int end = 100;
19 printf("I will begin\n");
20 int n = Sum(start, end);
(gdb) b 20
Breakpoint 1 at 0x11c3: file mycmd.c, line 20.
(gdb) r
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:20
20 int n = Sum(start, end);
(gdb) s
Sum (s=32767, e=-7136) at mycmd.c:5
5 {
(gdb) n
6 int result = 0;
(gdb) n
7 for(int i = s; i <= e; i++)
(gdb) n
9 result += i;
(gdb) display i
1: i = 1
(gdb) n
7 for(int i = s; i <= e; i++)
1: i = 1
(gdb) n
9 result += i;
1: i = 2
(gdb) n
7 for(int i = s; i <= e; i++)
1: i = 2
(gdb) n
9 result += i;
1: i = 3
(gdb)
7 for(int i = s; i <= e; i++)
1: i = 3
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
(gdb) b 9 if i == 30 # 9是⾏号,表⽰新增断点的位置
Breakpoint 2 at 0x555555555186: file mycmd.c, line 9.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
2 breakpoint keep y 0x0000555555555186 in Sum at mycmd.c:9
stop only if i == 30
(gdb) finish
Run till exit from #0 Sum (s=1, e=100) at mycmd.c:7
Breakpoint 2, Sum (s=1, e=100) at mycmd.c:9
9 result += i;
1: i = 30
(gdb) finish
Run till exit from #0 Sum (s=1, e=100) at mycmd.c:9
0x00005555555551d2 in main () at mycmd.c:20
20 int n = Sum(start, end);
Value returned is $1 = 5050
给已经存在的端点新增条件
cpp
(gdb) l main
11
12 return result;
13 }
14
15 int main()
16 {
17 int start = 1;
18 int end = 100;
19 printf("I will begin\n");
20 int n = Sum(start, end);
(gdb) b 20
Breakpoint 1 at 0x11c3: file mycmd.c, line 20.
(gdb) r
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:20
20 int n = Sum(start, end);
(gdb) s
Sum (s=32767, e=-7136) at mycmd.c:5
5 {
(gdb) n
6 int result = 0;
(gdb) n
7 for(int i = s; i <= e; i++)
(gdb) n
9 result += i;
(gdb)
7 for(int i = s; i <= e; i++)
(gdb)
9 result += i;
(gdb)
7 for(int i = s; i <= e; i++)
(gdb)
9 result += i;
(gdb)
7 for(int i = s; i <= e; i++)
(gdb) b 9 # 我们在第9⾏新增⼀个断点,⽤来开始测试
Breakpoint 2 at 0x555555555186: file mycmd.c, line 9.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
2 breakpoint keep y 0x0000555555555186 in Sum at mycmd.c:9
(gdb) n
Breakpoint 2, Sum (s=1, e=100) at mycmd.c:9
9 result += i;
(gdb) n
7 for(int i = s; i <= e; i++)
(gdb) n
Breakpoint 2, Sum (s=1, e=100) at mycmd.c:9
9 result += i;
(gdb) condition 2 i==30 #给2号断点,新增条件i==30
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
2 breakpoint keep y 0x0000555555555186 in Sum at mycmd.c:9
stop only if i==30
breakpoint already hit 2 times
(gdb) n
7 for(int i = s; i <= e; i++)
(gdb) n
9 result += i;
(gdb) c
Continuing.
Breakpoint 2, Sum (s=1, e=100) at mycmd.c:9
9 result += i;
(gdb) p i
$1 = 30
(gdb) p result
$2 = 435
注意:
条件断点添加常⻅两种⽅式:1. 新增 2. 给已有断点追加
注意两者的语法有区别,不要写错了。
新增: b ⾏号/⽂件名:⾏号/函数名 if i == 30(条件)
给已有断点追加:condition 2 i==30, 其中2是已有断点编号,没有if
本期内容就到这里了,如果有成功帮到你了请点个赞,感谢你们的支持,谢谢!
封面图自取:
