3. 处理数组越界访问
任务说明
本案例提供一个包含数组越界访问的C程序,该程序试图计算一个整数数组的“平均值”,但索引逻辑存在越界错误,导致读取了数组边界之外的内存。你的任务是定位越界位置,分析其后果,并实施修复,使程序只访问合法索引范围内的元素。
问题代码
#include <stdio.h>
#define SIZE 5
int main(void) {
int data[SIZE] = {10, 20, 30, 40, 50};
int sum = 0;
float average;
// 计算数组元素之和(存在越界)
for (int i = 0; i <= SIZE; i++) {
sum += data[i];
}
average = (float)sum / SIZE;
printf("平均值: %.2f\n", average);
return 0;
}
问题定位
循环条件 i <= SIZE 在 i = 5 时仍然执行循环体,此时访问 data[5]。数组 data 的合法索引范围是 0 到 4(共5个元素),因此 data[5] 属于越界访问。该操作读取了数组末尾之后的内存位置,其值是不确定的(可能是任意垃圾值),导致求和结果错误,进而使平均值计算不准确。在某些编译环境或运行场景下,这种越界还可能触发运行时错误(如栈保护检测),导致程序异常终止。
修复方案
将循环条件从 i <= SIZE 改为 i < SIZE,确保索引最大值为 SIZE - 1,即 4,从而只访问合法范围内的元素。
修复后代码
#include <stdio.h>
#define SIZE 5
int main(void) {
int data[SIZE] = {10, 20, 30, 40, 50};
int sum = 0;
float average;
// 正确遍历数组:i 从 0 到 SIZE-1
for (int i = 0; i < SIZE; i++) {
sum += data[i];
}
average = (float)sum / SIZE;
printf("平均值: %.2f\n", average);
return 0;
}
验证方法
使用支持地址检查的编译选项(如GCC的 -fsanitize=address)分别编译修复前后的代码,可以直观验证越界问题是否被消除:
# 编译修复前代码(应报告越界错误)
gcc -fsanitize=address -g before.c -o before
./before
# 编译修复后代码(应正常运行)
gcc -fsanitize=address -g after.c -o after
./after
修复前运行会触发AddressSanitizer的堆栈越界报告;修复后程序正常输出平均值。若不使用Sanitizer,修复前程序可能输出一个错误平均值(因越界读取到不确定值),修复后输出确定且正确的平均值。
预期输出
修复后代码的预期输出为:
平均值: 30.00
因为 (10+20+30+40+50)/5 = 150/5 = 30,该结果由代码逻辑直接唯一推出。
判定标准
- 修复后代码在编译时无警告(使用
-Wall -Wextra),运行时无越界访问。 - 使用AddressSanitizer编译修复后代码,程序正常退出且无任何错误报告。
- 程序输出的平均值为
30.00,与手算结果一致。