0114、安全开发篇(二):指针进阶、文件与项目实践
返回:C/C++ 进阶提升目录 · 安全开发总目录
这一部分把进阶指针、字符串函数重构、变量生命周期、文件读写和控制台项目放在一起整理。它更像一章“把 C 语言基础变成项目能力”的复盘:既要懂指针,也要能把数据保存下来,还要能用链表或结构体维护程序状态。
4.0 指针数组步长
指针数组是“数组里的每个元素都是指针”。常见于字符串列表、菜单项、回调表。
1 |
|
复盘重点:items 是数组,items[i] 是指针,items[i][0] 才是字符。
4.1 指针差值计算
两个指针相减得到的是元素个数差,不是字节差。前提是它们指向同一个数组范围。
1 | int nums[5] = {1, 2, 3, 4, 5}; |
4.2 指针基本特性
指针保存地址;指针类型决定解引用方式和步长;指针是否有效由生命周期和边界决定。
| 特性 | 复盘问题 |
|---|---|
| 地址 | 指向哪里 |
| 类型 | 按几个字节解释 |
| 步长 | p + 1 前进多少 |
| 生命周期 | 指向对象是否还活着 |
| 权限 | 目标内存能否读写 |
4.3 指针数组
指针数组适合维护多个字符串或多个对象地址。安全边界是:数组本身有长度,每个指针也要保证非空。
1 | void print_names(const char *names[], int count) { |
4.4 数组指针
数组指针是“指向数组的指针”。它常用于二维数组传参。
1 |
|
int (*matrix)[3] 表示指向“含 3 个 int 的数组”的指针。
4.5 指针函数
指针函数是“返回指针的函数”。返回值必须指向仍然有效的内存,不能返回局部变量地址。
1 | const char *status_text(int code) { |
4.6 函数指针
函数指针保存函数入口地址,常用于回调、菜单分发和策略表。
1 |
|
复盘重点:函数指针类型必须和真实函数签名一致。
4.7 字符与字符串
字符是单个值,字符串是以 \0 结尾的字符数组。字符串处理最大风险是边界和终止符。
| 内容 | 说明 |
|---|---|
'A' |
字符常量 |
"A" |
两个字符:A 和 \0 |
char * |
指向字符序列 |
char[] |
字符数组,可作为缓冲区 |
4.8 重构 sizeof
sizeof 是编译期运算符,不是函数。课程里重构更多是为了理解数组、指针和类型大小的差异。
1 | int nums[4]; |
4.9 重构 strlen
strlen 从字符串首地址开始数,直到遇到 \0。如果没有终止符,就会继续读到未知区域。
1 |
|
5.0 重构 strcmp
字符串比较不是只看长度,而是逐字符比较 ASCII/编码值,直到出现差异或都到结尾。
1 | int my_strcmp(const char *a, const char *b) { |
5.1 重构 strcat
拼接字符串一定要关心目标缓冲区容量。标准 strcat 不知道容量,项目里更推荐带长度的安全写法。
1 |
|
5.2 局部变量生命周期
局部变量在函数栈帧中存在,函数返回后就不应该继续访问它的地址。
错误示例:
1 | int *bad(void) { |
5.3 游戏基址的秘密
这节可以理解为“程序运行时地址不是固定不变的”。模块基址、偏移、动态分配地址、ASLR 都会影响地址稳定性。复盘时只记录授权样本中的地址关系,不把它用于未授权修改。
5.4 重复包含定义机制
头文件要用 include guard 或 #pragma once 防止重复包含。
1 |
|
5.5 静态变量特性
static 在不同位置含义不同:局部静态变量延长生命周期;文件作用域 static 限制链接范围。
5.6 寄存器变量
register 现在更多是历史语法,编译器会自己决定优化。复盘重点是理解“变量不一定真的长期存在内存中”,优化后可能在寄存器里。
5.7 枚举变量
枚举适合表达有限状态,比裸整数更清楚。
1 | typedef enum MenuAction { |
5.8 共享内存
共享内存用于多个进程访问同一段数据。安全复盘时要关注权限、同步、边界和数据一致性。
5.9 自定义数据类型
typedef 可以给复杂类型起别名,尤其是结构体、函数指针和回调类型。
6.0 结构体别名
结构体别名让项目代码更简洁,但不要为了短而牺牲语义。
6.1 函数指针类型
函数指针类型可以让菜单分发更清楚:
1 | typedef void (*MenuHandler)(void); |
6.2 文件数据样式
文件可以按文本看,也可以按二进制看。文本适合日志和配置,二进制适合结构化数据、图片、程序文件等。
6.3 文件指针数据
FILE * 表示一个打开的文件对象。打开后要检查,使用后要关闭。
1 | FILE *fp = fopen("data.txt", "r"); |
6.4 文件数据交互
文件读写要处理失败、边界和换行。不要默认读取一定成功。
6.5 文件内容读写
文本读写常用 fgets,二进制读写常用 fread / fwrite。任何读取都要检查返回值。
6.6 文件编码处理
编码问题会影响字符串长度、显示和解析。ASCII、GBK、UTF-8 在中文场景下尤其要区分。
14.7 - 15.8 项目:控制台游戏实践
这部分项目把前面的函数、结构体、链表、控制台输出和流程控制串起来。复盘时不用追求界面多花,重点是把项目拆清楚:
| 模块 | 复盘重点 |
|---|---|
| 控制台框架 | 窗口大小、刷新、输入处理 |
| 地图绘制 | 二维数组或坐标系统 |
| 节点管理 | 单链表维护对象 |
| 随机对象 | 随机数、边界和重复位置 |
| 操作流程 | 输入、移动、碰撞、死亡判断 |
| 运行检测 | 每一步都有可观察状态 |
学习检查清单
| 检查项 | 状态 |
|---|---|
| 能区分指针数组、数组指针、指针函数和函数指针 | 待复盘 |
能手写 strlen、strcmp 的基本版本并说明边界 |
待复盘 |
| 能说明局部变量地址为什么不能返回 | 待复盘 |
| 能用结构体和函数指针组织菜单逻辑 | 待复盘 |
| 能完成文件打开、读写、关闭和错误检查 | 待复盘 |






