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,与手算结果一致。
数组越界访问:问题与修复 数组 data[5] 内存布局 10 20 30 40 50 ? [0] [1] [2] [3] [4] [5] 越界 合法访问范围: 0 ~ SIZE-1 (0~4) 越界读取 修复前后循环条件对比 for (i=0; i <= SIZE; i++) // 越界 ✗ 访问 data[5] for (i=0; i < SIZE; i++) // 正确 ✓ 最大访问 data[4] 修复效果 ✓ 消除越界读取 ✓ 平均值正确为 30.00