Linux 栈中数组访问越界导致死循环现象

1.引言

最近在学习极客时间课程过程中碰到个挺有意思的关于数组和Linux 栈中增长方向的问题,特来与大家分享下。

话不多说,先上代码


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;
}

代码很简单。当使用gcc 搭配上-fno-stack-protector(禁用堆栈保护)选项进行编译、运行。小伙伴认为结果应该是什么?不加上-fno-stack-protector 选项结果又是什么?

 

2.现象

当加上-fno-stack-protector 编译选项后,运行时程序一直循环打印 hello world。

然而不加上-fno-stack-protector 编译选项,程序只打印四次 hello world 就退出。

 

3.解析

当加上-fno-stack-protector进行编译时,编译器是按照C语言以及Linux中规定的方式对变量进行处理。

首先简单了解下此问题涉及到的相关知识:

1)了解Linux 进程内存分配的小伙伴肯定熟悉下图,不熟悉的可以先看下,图中箭头表示堆栈的增长方向。

程序中的局部变量i和数组arr均存储在栈空间,而Linux中栈增长方向是递减的,因此先定义的变量i地址肯定大于数组arr地址。

假设i的地址为0xb200_0020,那么数组arr的起始地址就应该是0xb200_0014(arr[0] = 0xb200_0014、rr[1] = 0xb200_0018、arr[2] = 0xb200_001c)。可能会有小伙伴想为啥数组变量的内存分布一定是这样,而非下图左侧所示呢?

2)其实数组在Linux 栈内存中这样分配的原因在于C语言中数组的某个元素的地址由来相关。

C语言中数组某个元素地址计算公式如下所示:

a[i]_address = base_address + i * data_type_size

所有的元素都是在数组基地址的基础上 + 对应的偏移量,这就使数组中索引号越大的元素,其地址越大的原因。

结合上Linux 栈中的内存分配情况,共同就导致了变量i后紧跟的是a[2]而非a[0]了。

通过以上两点知识便知道这些变量在栈内存中的分布情况,相信聪明的小伙伴们能明白为什么程序会一直打印hello world。当i = 3时,&a[3]其实指向的就是变量i地址,此时执行a[3] = 0,意味着i = 0,导致无限循环。这就是一直打印hello world根本原因。

至于为啥不加上-fno-stack-protector程序打印四次hello world就退出,了解到gcc编译时默认就会打开堆栈保护选项。

简单的打印了下变量的地址:

上图中可看到默认情况下:变量i紧随在数组array[0]后面,意味着入栈顺序为:array[2]、array[1]、array[0]、i

加上-fno-stack-protector选项后:变量i后面紧随数组array[2],意味着入栈顺序为:i、array[2]、array[1]、array[0]

编译时禁不禁用堆栈为何会导致该问题,原因后续博文追踪。

 

4.总结

由于对数组的越界访问未加限制以及指针访问越界,此乃C语言设计本身的一大缺陷,同时也是C语言的魅力之处。

因此在日常使用过程,数组乃至指针等直接涉及内存相关的操作,一定要注意是否存在越界的情况。很多时候未加思考,导致后期出现的bug可能极难解决。C语言涉及内存使用时一定要多思考,尽可能减少这种隐藏bug。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值