数组越界会导致死循环吗?

本文深入分析了一道C语言编程题,在32位系统下运行特定程序时出现的死循环现象。通过观察变量i和数组a的地址,揭示了i越界导致的循环异常终止条件,解释了为何会出现多行重复输出的现象。

今天在牛客网上看到这样一道题,觉得比较有意思,也触及到了我的知识盲区,遂将其记录下来。题目内容如下:
在32位系统下运行以下程序,可能的输出结果为()

#include <stdio.h>
int main() {
    int i, a[5];
    for (i = 0; i <= 30; i++) {
        a[i] = 0;
        printf("%d:hello\n", i);
    }
    printf("%d:hello world", i);
    return 0;
}

A.三十行的 i:hello(i∈[0,30])和一行 30:hello world
B.三十行的 i:hello(i∈[0,30])和一行 31:hello world
C.多行的 i:hello(i∈[0,30])
D.多行的 i:hello(i∈[0,31])
下面是程序的运行结果
在这里插入图片描述
答案显而易见了,是 C,会出现多行的 i:hello(i∈[0,30])
但是什么会出现这种情况呢?
我们在程序中加一条辅助语句,打印出 i 和 a 的地址
在这里插入图片描述
看运行结果
在这里插入图片描述
我们可以清楚的发现,i 的地址和数组 a 第一个元素的地址只差了20,20 = 4 * 5,正好是相差了5个整形的大小,所以当循环到i = 6时,a[6] = 0,此时的 a[6] 也就是 i,因为它们的地址是一样的。此时,i 重新赋值为0,循环回到起点,导致死循环。
补充
栈中是从高地址指向低地址的,如下:
高地址 | i | a[4] | a[3] | a[2] | a[1] | a[0] | 低地址
有些编译器会做优化,使数组和i之间留有内存间隙,所以具体能够使循环正常退出的 i 的条件是不一定的,但是可以肯定的是,如果 i 越界严重,比如不小心给了30,还是会导致死循环。而我的编译器是没有进行优化的,所以会出现多行的 i:hello(i∈[0,4])的死循环。

<think>我们正在讨论OJ编程中避免数组越界死循环等常见错误的方法。参考引用的内容,我们可以从以下几个方面来分析和总结: 1. **数组越界**:这是OJ中RuntimeError的常见原因之一。根据引用[2],数组开得太小会导致访问到不该访问的内存区域。另外,引用[4]提到,在使用vector构建二维数组时,如果没有正确初始化每一维的大小,直接使用下标访问会导致错误。 2. **死循环**:虽然没有直接引用提到死循环,但死循环通常是由于循环条件设置不当造成的,例如循环变量未正确更新,或者循环退出条件不满足。 3. **其他常见错误**:引用[2]还提到了除零错误、大数组定义在函数内导致栈溢出、指针错误等。 因此,我们可以从以下几个方面给出避免这些错误的方法: ### 一、避免数组越界 1. **数组大小声明**: - 静态数组:在声明数组时,确保数组大小足够。根据题目要求,通常要比最大数据规模稍大一些(例如+10)。例如,题目说明$n \leq 1000$,则可以声明为`int arr[1010];`。 - 动态数组(STL vector):使用`vector`时,如果已知大小,应提前使用`resize()`或构造函数初始化大小。引用[4]中的例子说明了二维`vector`的初始化方法: ```cpp vector<vector<int>> res(M.size()); // 第一维大小确定 for (int i = 0; i < res.size(); i++) res[i].resize(M[0].size()); // 初始化第二维 ``` 2. **下标访问检查**: - 在访问数组元素前,确保下标在合法范围内($0 \leq \text{index} < \text{size}$)。特别是循环中的边界条件,注意是使用`<`还是`<=`。 - 对于字符串,注意不要访问`size()`位置(C++中字符串末尾是`size()`,访问该位置会导致未定义行为,尽管使用`at()`会抛出异常,但一般使用下标访问时,循环条件应为`i < str.size()`)。 ### 二、避免死循环 1. **循环条件设置**: - 确保循环变量在循环体内得到更新,并且最终能使循环条件不满足。 - 注意循环变量的增减方向是否与条件匹配(例如,循环变量递增时,条件应为小于某个值;递减时,条件应为大于某个值)。 2. **特殊情况的处理**: - 考虑边界情况,例如当输入为0或负数时,循环是否仍然能正确退出。 ### 三、其他OJ常见错误规避 1. **除零错误**: - 在除法运算前,务必检查除数是否为0。特别是在模运算(`%`)和除法(`/`)中。 2. **栈溢出**: - 避免在函数内部定义非常大的局部数组(如`int arr[1000000];`)。引用[2]提到这会导致程序栈区耗尽。解决方法: - 使用动态分配(如`vector`)或全局数组(静态存储区)。 3. **指针错误**: - 在使用指针时,确保指针指向有效的内存区域。避免野指针和空指针的解引用。 4. **输入输出格式**: - 根据引用[1],OJ通常有多组测试数据,因此需要循环读取输入直到文件末尾。例如使用`while(cin>>n)`或`while(scanf("%d", &n) != EOF)`。 - 输出格式必须严格按照题目要求,包括空格、换行等。 5. **类名和主函数**: - 引用[3]中提到,在Java中类名必须为`Main`,而在C++中主函数必须是`int main()`。确保没有拼写错误。 ### 四、调试技巧 - **小数据测试**:使用题目给出的样例测试,并自己构造边界数据(如最小输入、最大输入、特殊值等)。 - **输出中间变量**:在循环中打印关键变量的值,帮助定位错误。 - **静态代码检查**:逐行检查代码,特别注意循环和数组下标。 ### 五、代码规范建议 1. **变量命名**:使用有意义的变量名,避免使用过于简单的单字母变量(除非在循环中常用的`i, j, k`)。 2. **代码缩进**:保持一致的缩进风格,增加可读性。 3. **注释**:对关键步骤添加注释,便于自己检查和他人理解。 ### 示例:避免数组越界的代码 ```cpp #include <iostream> #include <vector> using namespace std; int main() { int n; while (cin >> n) { // 循环输入多组数据[^1] // 静态
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值