今天记录一个错误的程序,原因是这个程序可以引出几个小的知识点,有助于初学者更好学习C语言。好了,直接看程序。
问题引出
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
看了这段代码,大家觉得运行结果是什么呢?我认为对于这段代码,90%以上的初学者都能看出代码是有问题的,很明显定义的数组只有10个元素,而在for循环里面一直可以访问到第13个元素,这是一个数组越界问题,但是如果让你说出这段代码的运行结果,我相信很多初学者会感到无法预料,我第一次看到这段代码就是这个感觉。
运行结果
直接说结果:这段代码会陷入死循环。很多人可能想不通为什么是这个结果(当然我指的是初学者)。
调试
接下来,我通过一个初学者的角度来分析一下这段代码,不想看这个可以直接跳转到结论。
第一步: 启动调试。在VS编译器中,按F10进入调试,打开监视窗口,然后添加要监视的变量i和 arr,因为根据for循环中的条件,一直能访问到arr[12],因此把arr[10]、arr[11]、arr[12] 也添加到监视中。如下图
第二步:开始调试,按F11一步步调试,调试过程中,我们能发现一个问题,i的值与arr[12]的值保持同步,当i=12时,如下图
第三步:分析。我们发现,a[12] 被赋为0,i此时也变为了0,因此,无法跳出循环,这就是为什么程序运行结果为死循环的原因!
那么为什么会这样呢?这就引出了另一个知识点:数据存储,我们知道在内存中局部变量存储在栈区,i 和 arr就存在栈区。下面需要明确两个知识点:
- 栈区的使用方式:先使用低地址空间,再使用高地址空间。
- 数组的存储方式:随着数组下标增长,地址由低到高变化。
结论
我们先定义i放入栈中,再定义数组arr放入i下方,arr的地址是低于i的,从低到高访问数组,一旦越界,就会访问到i的地址,arr[12]的地址与i一样,指的是同一块内存! 因此当循环到i=12时,arr[12] 被赋为0,也即i被赋为0,这样的话永远也跳不出循环!如下图所示。
这个时候肯定很多初学者(曾经的我)会有疑问:我没定义arr[12],为什么会访问到arr[12] ? 这就又引出另外一个小知识点:关于数组的定义。
我们需要知道 arr[i] 的含义,arr[i] <==>*(arr+i),所以arr[10] 这个数组也可以这样表示:10[arr]。(但必须注意不能这样定义数组,可以这样访问) arr[12] 其实就是指:地址为(arr+12)的这块内存的值 。C/C++对数组下标是不检查的,所以数组越界访问不会报错。
那么还有一个问题,i和数组arr在栈上的位置隔了两个整型空间,这是偶然还是必然?这个取决于编译器的内存分配方式,VC6.0编译器i和arr的地址是挨着的,VS空了两个整型,Linux下空了1个整型。
OK,就先写到这吧。