C 数组越界与边界检查
更新时间:2026-08-26。本文是
languages/c/主题入门层第 25 篇。大多数语言里"数组越界"会立刻报错;C 里什么都不会发生——直到程序崩溃,或者数据悄悄被改坏。这一篇讲清楚这个危险的特性,以及怎么保护自己。
本文要回答的问题
arr[10]越界了,C 会报错吗?- 越界到底会破坏什么?
- 怎么写出不越界的代码?
一、C 不检查越界
c
int arr[5] = {1, 2, 3, 4, 5};
arr[5] = 99; // 越界!下标 0~4 才对
arr[100] = 1; // 更离谱的越界
printf("%d\n", arr[5]); // 读越界C 编译不报错,运行时也不检查——arr[i] 就是"起始地址 + i × 4"直接算地址访问。越界发生在哪,取决于地址上碰巧是什么:
| 越界访问的地址落在哪 | 后果 |
|---|---|
| 相邻变量 | 悄悄改坏其他数据(最阴险) |
| 未映射的内存页 | 段错误,进程崩溃 |
| 返回地址 / 关键指针 | 程序行为错乱,可能被利用攻击 |
越界是未定义行为(UB):程序不是"一定会崩",而是"行为不可预测"——今天正常,明天换个编译器就崩,这正是最危险的地方。
二、缓冲区溢出:越界的"黑暗面"
越界写不只是崩溃问题,还是安全漏洞。经典例子:
c
char password[8];
strcpy(password, "this-is-a-very-long-password"); // 远超 8 字节!strcpy 不管目标多大,一个字节一个字节往里写,直到字符串写完。多出来的字节会覆盖栈上相邻的数据——可能刚好覆盖函数的返回地址。攻击者精心构造输入,就能让程序跳转到恶意代码——这就是缓冲区溢出攻击的原理(缓冲区溢出与安全深入讲)。
这就是为什么上一课强调:传数组给函数要带长度,写数据要检查够不够放。
三、防御性编码:三条纪律
纪律 1:遍历永远带边界。循环写 i < n 而不是 i <= n 或裸的 for(;;):
c
int arr[5];
for (int i = 0; i < 5; i++) { // 正确边界:0..4
arr[i] = i * 2;
}纪律 2:写入前检查空间:
c
char buf[16];
const char *msg = "hello world";
if (strlen(msg) < sizeof(buf)) { // 放得下才写
strcpy(buf, msg);
}
// 更稳的写法:strncpy(buf, msg, sizeof(buf) - 1)纪律 3:用带长度限制的安全函数。strcpy → strncpy(或更好用 snprintf)、sprintf → snprintf、gets → fgets。snprintf 家族永远带 sizeof:
c
char buf[16];
snprintf(buf, sizeof(buf), "数值是 %d", 12345); // 写不下就截断,不溢出四、工具辅助:编译器与运行时检查
| 工具 | 作用 | 用法 |
|---|---|---|
-Wall -Wextra | 警告可疑的固定越界 | 编译选项 |
-fsanitize=address | 运行时检测越界(ASan) | gcc -fsanitize=address |
-fstack-protector | 栈溢出检测 | 编译器默认常开 |
| gdb / core dump | 定位崩溃现场 | 见 crash 主题 |
强烈推荐开发期用 ASan:
bash
gcc -O0 -g -fsanitize=address -o prog prog.c
./prog
# 越界时会打印详细报告:哪行代码、越界多少字节ASan 能在开发阶段抓住绝大多数越界,是 C 程序员的"行车记录仪"。本站 crash 主题 有完整指南。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 固定大小数组越界 | 偶尔崩溃 / 数据错乱 | 边界检查 + ASan |
strcpy 不限制长度 | 缓冲区溢出 | snprintf/strncpy |
i <= n 写遍历 | 多写一个元素 | i < n |
| 越界读 | 读到垃圾值 | 同样检查边界 |
| 依赖"碰巧没崩" | 上线才炸 | 开发期开 ASan |
六、与本站主线衔接
- 缓冲区溢出的完整机制与攻击原理,见高手层缓冲区溢出与安全;
- ASan 检测原理(影子内存),见 crash/ASan 主题;
- 数组与指针边界的关系,见指针与数组。
一句话总结
C 不检查越界,越界是 UB——可能悄悄改坏数据,也可能成为攻击入口;三条纪律(遍历带边界、写入前检查、用带长度的安全函数)加一个工具(开发期开 ASan),就能把数组越界这个 C 最大风险关进笼子。