数组-连续内存 数据结构与算法(王争)
2018-10-30 本文已影响0人
AibWang
数组:一块连续的内存空间
来存储一组具有'相同数据'类型的数据。数组的数据结构属于线性表
。
int main(int argc, char *argv){
int arr1[3] = {0.0};
for(int i=0;i<=3;i++){
arr1[i] = 0;
printf("i=%d %d hello world!!\n", i,arr1[i]);
}
// return
return(0);
}
运行上述C代码,发现是一个无限循环,为什么会造成无限循环呢?原因有两点,一是数组arr1
存储在一个连续的内存空间,二是for循环中对数组元素的引用超出了数组的下标范围。实际上这个bug很巧妙,数组arr1
的大小为3,在for循环中控制循环的条件为i<=3
,当i=3
时,索引超出了数组arr1
的范围,但是该地址是数组末尾地址加1 byte,这刚好是变量'i'的地址(至于为什么,我也不清楚),刚好我们将此地址的数值赋值为0
,又使其满足循环条件,再次进入循环就这样无限循环下去了。
如果我们将arr1[i] = 0
改为arr1[i] = i*i
,程序将不再为死循环,最后一次输出的i
等于9。
查阅评论区关于为什么arr1[3]
的地址就是i
的地址?评论区真是大神很多
- 问题涉及到函数调用的栈桢结构细节(操作系统或计算机体系结构的教材应该会讲到)。函数体内的局部变量存在栈上,且是连续压栈。在Linux进程的内存布局中,栈区在高地址空间,从高向低增长。变量
i
和arr1
在相邻地址,且i
比arr1
的地址大,所以arr1
越界正好访问到i
。当然,前提是i
和arr1
元素同类型,否则那段代码仍是未决行为。- 栈是向下增长的,首先压栈的i,a[2],a[1],a[0],这是我在我vc上调试查看汇编的时候看到的压栈顺序。相当于访问a[3]的时候,是在访问i变量,而此时i变量的地址是数组当前进程的,所以进行修改的时候,操作系统并不会终止进程。
- 和编译器的实现有关,
gcc
有一个编译选项(-fno-stack-protector
)用于关闭堆栈保护功能。默认情况下启动了堆栈保护,不管i声明在前还是在后,i都会在数组之后压栈,只会循环4次;如果关闭堆栈保护功能,则会出现死循环. (https://www.ibm.com/developerworks/cn/linux/l-cn-gccstack/index.html)
当我们改变定义arr1
和i
的顺序,程序依然是一个死循环,这还得归于栈的内存顺序问题。
注意事项
并不是所有的编程语言都把数组越界检查交给程序员来做,比如说python,java,如果数组的引用越界,程序会报错。