数组-连续内存 数据结构与算法(王争)

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的地址?评论区真是大神很多

  1. 问题涉及到函数调用的栈桢结构细节(操作系统或计算机体系结构的教材应该会讲到)。函数体内的局部变量存在栈上,且是连续压栈。在Linux进程的内存布局中,栈区在高地址空间,从高向低增长。变量iarr1在相邻地址,且iarr1的地址大,所以arr1越界正好访问到i。当然,前提是iarr1元素同类型,否则那段代码仍是未决行为。
  2. 栈是向下增长的,首先压栈的i,a[2],a[1],a[0],这是我在我vc上调试查看汇编的时候看到的压栈顺序。相当于访问a[3]的时候,是在访问i变量,而此时i变量的地址是数组当前进程的,所以进行修改的时候,操作系统并不会终止进程。
  3. 和编译器的实现有关,gcc有一个编译选项(-fno-stack-protector)用于关闭堆栈保护功能。默认情况下启动了堆栈保护,不管i声明在前还是在后,i都会在数组之后压栈,只会循环4次;如果关闭堆栈保护功能,则会出现死循环. (https://www.ibm.com/developerworks/cn/linux/l-cn-gccstack/index.html)

当我们改变定义arr1i的顺序,程序依然是一个死循环,这还得归于栈的内存顺序问题。

注意事项

并不是所有的编程语言都把数组越界检查交给程序员来做,比如说python,java,如果数组的引用越界,程序会报错。

https://time.geekbang.org/column/article/40961

上一篇下一篇

猜你喜欢

热点阅读