1.题目的展示
最近看到了一道非常坑的题目,如下
在VS2022、X86、Debug的环境下,编译器不做任何优化的话,下⾯代码执⾏的结果是啥?
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<=12; i++)
{
arr[i] = 0;
printf("hehe %d\n",i);
}
return 0;
}
看到这段代码,第一反应就是会发生数组越界访问,因此可能认为这段代码会崩溃,事实上,可能在其他环境下确实是这样,但是在上述 VS2022、X86、Debug的环境下,会发生另一个奇特的现象。
可以看到死循环了。
2.猜想
可以看到死循环中i每次变成11后就变回了0,让我们调试一下看看
这是i==11时的情况
当我们结束第11次循环后,进行到图中137行代码时,惊奇的发现arr[i]变得和i的值一样了!
这引发了一个猜想:难道i和arr[11]的地址是一样的?
3.简单解释
要验证这个猜想是否正确,我们先来了解下面的知识。
我们经常会在研究问题时将内存划分为三个部分:栈区,堆区,静态区,栈区用于局部变量创建和函数栈帧创建,堆区用于动态内存分配,静态区存放全局变量和静态变量。这道题只创建了main函数的栈帧(函数再调用前需要在栈区开辟栈帧,main函数自然也不例外)和一些局部变量,自然要研究一下栈区。分析这段代码的执行过程。
栈区在使用时,一般都是先使用高地址,再使用低地址。那么就会先创建i ,在创建arr数组。
调出vs的监视窗口
可以发现确实i的地址比arr[0]大。
接下来代码继续执行,arr[1],arr[2],arr[3].......的值变为0,直到arr[9]变成0,然后就开始访问非法内存了,arr[10]实际上并不存在,若要继续执行arr[i]=0的指令,就会访问arr[9]后的一个地址,将其值变为0,像这样非法访问到arr[11]后,到了arr[12],在这时再执行arr[i]=0的指令,就发生了i和arr[12]都为12的情况。
我们通过监视窗口看到i的地址确实与arr数组首元素后第12个地址相同。那么就造成了i永远会小于或等于12,因此死循环。
4.简单回顾与总结
事实上,这道题的情况非常特殊,因为i的地址恰好与arr[0]的地址相差12,而其他很多情况下,i与arr[11]之间会有空隙,这样都无法造成死循环的结果。而这种巧合的出现,是依赖于VS2022、X86、Debug这种特定的环境。可以看到bug的产生是多么无孔不入,但凡换个环境,都不会死循环,而唯有借助调试的力量,才能有解决这种bug的机会。