首先,分析以下C语言代码的运行结果:
int main(int argc, char* argv[]){
int i = 0;
int arr[3] = {0};
for(; i<=3; i++){
arr[i] = 0;
printf("hello world\n");
}
return 0;
}
我们想要的结果是打印三行hello world,但是以上代码实际的运行结果是编译成功,无限循环打印"hello world"。
代码中的数组大小为3,a[0],a[1],a[2],而我们的代码因为书写错误,导致for循环的结束条件错写为了i<=3而非i<3,所以当i=3时,数组a[3]访问越界。
我们知道,在C语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的。a[3]会被定位到某块不属于数组的内存地址上,而这个地址正好是存储变量i的内存地址,那么a[3]=0就相当于i=0,所以就会导致代码无限循环。
解析:i和arr都是局部变量,存放在栈空间,栈中分配内存时是向下增长,从高地址向低地址分配,所以i的地址比arr高,而数组在内存中的地址是按序号从低地址向高地址存放的,在内存中永远满足a[0]所在的地址是低地址,而高序号元素占用的是高地址空间!所以以上代码中a[3]和i是相同的地址。
数组越界在C语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。因为,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。
这种情况下,一般都会出现莫名其妙的逻辑错误,就像我们刚刚举的那个例子,debug的难度非常的大。而且,很多计算机病毒也正是利用到了代码中的数组越界可以访问非法地址的漏洞,来攻击系统,所以写代码的时候一定要警惕数组越界。
但并非所有的语言都像C一样,把数组越界检查的工作丢给程序员来做,像Java本身就会做越界检查,比如下面这几行Java代码,就会抛出java.lang.ArrayIndexOutOfBoundsException。
int[] a = new int[3];
a[3] = 10;
C++ STL中的vector在有些情况下也支持越界检查:
关于 std::vector 的下标越界检查