本文为 Linux 开发工具专题的完结篇,深入讲解 GDB 调试器的进阶命令(断点管理、单步执行、查看变量、条件断点、watch 监视、set 修改变量等),并正式进入系统编程阶段:冯·诺依曼体系结构、操作系统定位、以及"先描述再组织"的管理思想。帮助同学们从工具使用平滑过渡到底层原理。
一、GDB 调试器进阶
1.1 复习:基本 GDB 命令
上节课我们学习了以下 GDB 命令:
| 命令 | 简写 | 作用 |
|---|---|---|
list |
l |
显示源代码 |
break |
b |
设置断点(行号/函数名) |
run |
r |
运行程序 |
next |
n |
逐过程(不进入函数,类似 F10) |
step |
s |
逐语句(进入函数,类似 F11) |
continue |
c |
继续执行到下一个断点 |
quit |
q |
退出 GDB |
使用
cgdb可以获得分屏界面(上屏代码,下屏命令),体验更好。按Esc可以切换到上屏,在按i切换回命令行。
1.2 断点管理进阶
1.2.1 查看断点信息
info breakpoints # 或 i b
输出示例:
Num Type Disp Enb Address What 1 breakpoint keep y 0x4005a2 main 2 breakpoint keep y 0x4005c8 sum
-
Num:断点编号 -
Enb:是否使能(y/n)
1.2.2 删除断点
delete 断点编号 # 简写 d
注意 :删除断点必须用编号,不能用行号。
1.2.3 使能/禁用断点
disable 断点编号 # 禁用断点(断点还在,但不会触发) enable 断点编号 # 重新使能断点
为什么要禁用而不是删除?
调试时有些断点暂时不需要,但以后可能还会用。如果删掉,下次还要重新设置位置。禁用它,痕迹保留,下次直接启用即可。
1.3 查看函数调用栈
backtrace # 或 bt
当程序在断点处暂停时,bt 可以显示当前函数的调用链(栈帧),帮助理解函数调用关系。
1.4 查看变量与表达式
| 命令 | 作用 |
|---|---|
print 变量名 |
打印变量的当前值(简写 p) |
print 表达式 |
计算表达式的值,如 p result * flag |
display 变量名 |
常显示变量,每次单步后自动打印(类似 VS 的监视窗口) |
undisplay 编号 |
取消常显示(也可用 info display 查看编号) |
info locals |
打印当前函数内所有局部变量的值 |
1.5 函数执行控制
| 命令 | 作用 |
|---|---|
finish |
执行完当前函数,返回到调用处 |
until 行号 |
继续执行,直到到达指定行(常用于跳出循环) |


示例 :
在 sum 函数循环内想直接跳到循环后,可以用 until 16(假设第16行是循环结束后的代码)。
1.6 修改变量的值
set var 变量名 = 新值
场景 :怀疑某个变量的值不对,想临时改一下验证假设。例如发现 flag 应该是1却为0,可以 set var flag = 1 继续运行,观察结果是否正常。这能快速定位问题原因,而不必重新编译。
1.7 监视变量变化(watch)
watch 变量名
-
设置一个监视点 ,当被监视的变量被修改时,程序会自动暂停并提示旧值和新值。
-
常用于排查"某个变量不应该被修改却意外变化"的问题。
-
监视点也是一种断点(watch point),可用
info breakpoints查看,用delete删除。
1.8 条件断点
场景 :循环执行100次,只想在 i == 50 时停下来观察。
方法一:设置断点时直接带条件
break 13 if i == 50
方法二:为已存在的断点添加条件
condition 断点编号 i == 50
-
断点编号通过
info breakpoints查看。 -
取消条件:
condition 断点编号(不带表达式)
1.9 调试小技巧总结
| 需求 | 命令 |
|---|---|
| 快速定位问题区域 | 用断点将代码分成块,continue 跳过无问题块 |
| 进入函数内部单步 | step(s) |
| 不进入函数直接执行完 | next(n) |
| 跳出当前函数 | finish |
| 跳出循环 | until 行号 |
| 临时修改变量验证 | set var |
| 监视变量变化 | watch |
| 条件断点 | break 行号 if 条件 |
二、冯·诺依曼体系结构
2.1 基本组成
冯·诺依曼体系结构是现代计算机的经典硬件组织方式,包含:
-
输入设备:键盘、鼠标、网卡、磁盘(输入数据到内存)
-
输出设备:显示器、网卡、磁盘(从内存输出数据)
-
存储器 :即内存(主存)
-
运算器:CPU 中负责算术运算和逻辑运算的部分
-
控制器:CPU 中负责取指令、分析指令、执行指令的部分

关键点 :CPU 只和内存打交道(数据层面),外设也只和内存打交道。数据流动的本质是从一个设备拷贝到另一个设备。
2.2 为什么程序必须先加载到内存?
-
程序(可执行文件)在运行前存放在磁盘(外存)上。
-
CPU 无法直接访问磁盘,它只能从内存中读取指令和数据。
-
因此,程序必须先由操作系统从磁盘拷贝到内存,CPU 才能执行它。
2.3 为什么要有内存?------ 性价比的平衡
-
离 CPU 越近的存储设备越快、越贵、容量越小(寄存器、缓存)。
-
离 CPU 越远的设备越慢、越便宜、容量越大(磁盘)。
-
如果没有内存,CPU 直接与磁盘交互,磁盘的慢速(毫秒级)会严重拖慢 CPU(纳秒级),导致整机效率由外设决定(木桶原理)。
-
内存的速度介于寄存器和磁盘之间,成本适中,作为缓冲层可以大幅提升系统整体性能。
历史意义 :冯·诺依曼体系结构让计算机变得便宜且高效,普通老百姓买得起,互联网才得以普及。
2.4 数据流动的例子:QQ 聊天
-
你通过键盘输入"你好",数据从键盘拷贝到内存。
-
QQ 程序(已在内存中)处理数据(加密、封包),CPU 参与计算。
-
处理后的数据从内存拷贝到网卡,发送到网络。
-
对方网卡收到数据,拷贝到对方内存。
-
对方 QQ 程序解密、解包,将"你好"从内存拷贝到显示器显示。

整个过程完全符合冯·诺依曼体系:外设 ↔ 内存 ↔ CPU。
三、操作系统(OS)初步
3.1 什么是操作系统?
-
狭义:操作系统内核(Kernel),负责进程管理、内存管理、文件管理、设备管理。
-
广义:内核 + 外壳程序(shell、图形界面)+ 系统调用 + 标准库 + 预装应用。

安卓的底层是 Linux 内核,安卓本身是运行在内核之上的"外壳"和库的集合。
3.2 为什么要有操作系统?
-
向下:管理软硬件资源(CPU、内存、磁盘、网卡等)。
-
向上 :为应用程序和用户提供良好、稳定、高效的运行环境。
3.3 软硬件体系结构的层状设计
-
高内聚、低耦合:每层各司其职,修改一层不影响其他层。
-
硬件坏了换硬件,驱动坏了换驱动,内核升级不影响上层应用。
3.4 系统调用与库函数
-
用户程序不能直接访问操作系统内核,必须通过系统调用(System Call)接口。
-
系统调用使用起来比较底层、复杂,因此语言标准库(如 C 标准库)对系统调用进行了封装,提供更友好的函数(如
printf、scanf)。
你写的
printf("hello")底层会调用write系统调用,最终由操作系统将数据写入显示器。
3.5 管理的本质:先描述,再组织
3.5.1 故事:校长如何管理几万名学生?
-
校长不需要和每个学生见面。
-
校长通过数据(学生的姓名、成绩、宿舍号等)来管理。
-
数据由辅导员 (相当于驱动程序)收集并录入教务系统。
-
校长的日常工作变成对教务系统数据的增删查改。
3.5.2 计算机中的管理
-
操作系统管理硬件:为每种硬件定义
struct device结构体(描述),将所有设备组织成链表(组织)。 -
操作系统管理进程:为每个进程定义
struct task_struct(描述),将所有进程组织成链表(组织)。
结论 :任何管理工作(无论是学校、公司还是操作系统)都可以归结为两步:先描述,再组织。
3.5.3 为什么 C++ 要有类和 STL?
-
类:解决"描述"问题(将现实世界的对象用结构体/类表示)。
-
STL 容器和算法:解决"组织"问题(如何存储和操作这些对象)。
所以面向对象语言成为主流,不是因为语言设计者拍脑袋,而是因为世界本身就是先描述再组织的。
四、本节课总结
4.1 GDB 命令汇总(进阶)
| 命令 | 作用 |
|---|---|
info breakpoints |
查看所有断点/监视点 |
disable/enable 编号 |
禁用/启用断点 |
bt |
查看函数调用栈 |
p 变量 |
打印变量值 |
display 变量 |
常显示变量 |
info locals |
显示当前函数局部变量 |
finish |
执行完当前函数 |
until 行号 |
运行到指定行 |
set var 变量=值 |
修改变量 |
watch 变量 |
监视变量变化 |
condition 编号 条件 |
为断点添加条件 |
4.2 冯·诺依曼体系要点
-
CPU 只与内存交互,外设也只与内存交互。
-
数据流动本质是拷贝。
-
内存的存在平衡了速度与成本,是现代计算机性价比的关键。
4.3 操作系统核心思想
-
操作系统是软硬件资源的管理者。
-
管理的方法:先描述(结构体),再组织(数据结构)。
-
系统调用是用户程序访问内核的唯一通道。
-
库函数(如 printf)封装了系统调用。








