打印
[DemoCode下载]

指针过界引起全局变量改变的错误

[复制链接]
882|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
gejigeji521|  楼主 | 2016-3-22 23:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
指针过界引起全局变量改变的错误
最近,在写程序的时候,碰到一个在自己看来非常不可思议的问题。当然,或者高手就觉得大惊少怪了,呵呵。

  以下是问题相关:
  平台:MEGA64;
  编译环境:codeVisonAVR;
  问题:一个全局变量在定义的时候直接赋值为0,但在程序运行过程中,没有改变该变量的操作,可是奇怪的是,程序在运行一段时间之后,这个全局变量居然自动改变了。
  发现问题之后,我很难理解,一直无法找到原因。
  一开始,我就怀疑是不是哪里的存储空间溢出重叠了,但由于对各种变量的存储位置,不是十分的了解,也就没有再对此做更深层的探究;其次,我怀疑是不是哪里的指针跳错了,跳到全局变量那里,造成全局变量的改变,不过对于这一点,我又自认很仔细的检查了程序,的确没有发现指针跳错的地方。
  现在看来,其实,我的两个怀疑,都和指针有关的。
  后来,和志刚讨论一下,他一开始就提出:是不是堆设置不够。他说的堆是指hardware stack,我一开始不知道什么是hardware stack的。我猜他的意思是说,用于程序返回堆栈的空间太小了,以至于,硬件堆栈溢出,覆盖了全局变量,自然造成全局变量的改变。
  一开始我没有弄懂他的意思。明白之后,我觉得很有可能,不过,仔细看编译结果,又大概可以排除这个可能了,以下是编译结果:
Bit variables area: 2h to 2h
Bit variables size: 1 byte(s)
Data Stack area: 100h to 4FFh
Data Stack size: 1024 byte(s)
Estimated Data Stack usage: 98 byte(s)
Global variables area: 500h to 5DCh
Global variables size: 221 byte(s)
Hardware Stack area: 5DDh to 10FFh
Hardware Stack size: 2851 byte(s)
  从最后一行可知,硬件堆栈大得很,4K的SRAM,已占2.8K。一般来说,就算是函数多层嵌入也不可能占如此之大的空间,网上说,就算浮点函数,40B都已经足够。
  不过,由于志刚的提示,让我注意到上面的编译结果,以前我不知道全局变量是怎样存储的,现在就明白了,上面倒数3、4行,就是说全局变量的。可见,全局变量是保存在数据堆栈与硬件堆栈的中间,刚才志刚说,可能硬件堆栈溢出,造成全局变量的改变。虽然在这里这个可能不大了,但是,同理,会不会是数据堆栈溢出,造成全局变量的改变呢?因为,由上面几行编译结果可知,全局变量正处于数据堆栈和硬件堆栈之间,两者的溢出皆有可能造成全局变量的改变。不过,“Estimated Data Stack usage: 98 byte(s)”,这里又很明显提示,估算的数据堆栈实际用到的大小只有98B,也远远小于1024B。
  所以说,不管是数据堆栈还是硬件堆栈,两者都很难简单的溢出。
  下面是我在codeVisionAVR的帮助文档上面找到关于RAM的结构图:


  可知,codeVisionAVR是如何分配SRAM的(其它编译器,各不相同的),这里简单述说,以备以后参考。以maga64为例。(芯片资料说的4K SRAM,是不包括地址前面的100B的,也就是说SRAM的大小从数据堆栈开始数起)
  首先是,32个工作寄存器+64个I/O寄存器,这里占了RAM地址分配的前100字节,0H-99H;
  其次是,数据堆栈,而且是由高往低堆的,也就是,先从地址高处往低处进栈,由于它是用Y寄存器来做数据堆栈的指针的,所以,数据堆栈就从Y的初始值开始,到地址100H结尾,这个大小可以在编译器工程设置里面设置,一般可以先编译程序,看编译估算的实际数据栈使用大小,再去定数据堆栈的大小,自然要定大一点,防止溢出。主要用于动态储存局部变量、函数参数和中断时各工作状态寄存器的值;
  接着是,全局变量区域,这个是编译器通过统计程序的全局变量数量而定的。用于保存全局变量;
  再接着是,硬件堆栈,以SP初始值开始,到全局变量最高地址为止,而且和数据堆栈一样,也是由高往低堆的。用于保存函数返回地址;
  最后是,堆(heap),是malloc, calloc, realloc and free等链表函数用来建立链表的内存空间,如果不使用这个函数,必须设置为0.
  由上面可知,由于全局变量处于数据堆栈和硬件堆栈中间,而后两者都是由高往低进栈的,所以说,如果正常溢出,只能是硬件堆栈溢出才可以造成全局变量的改变,而数据堆栈的溢出不可能造成这个问题。可是现在硬件堆栈这么多,也几乎不可能是它溢出造成这个问题的。
  那会是什么原因造成上面那个问题的呢?分析之后,我重新再仔细查找程序,最后还是找到了原因。还好,这些程序原先不是我写的,呵呵。
  有一个以数组的地址指针为参数的函数example(char *pTemp),主函数调用它的时候,传一个4位数组temp[4]给它,如:example(temp);
  下面大略的代码:
  main()
  {
    char temp[4];
    example(temp);
  }
  length[]={1,2,4};
  example(char *pTemp)
  {
    cLength=length[getIndex()];
    for(i=0;i<clength;i++)
    {
      *pTemp=readvalue();
      pTemp++;
    }
  }
  由于length是一个3位数组,如果getIndex()的值大于2,就造成cLength得到的值不在{1,2,4}内,而是储存length[]={1,2,4}往后地址的值,这个值是不确定的,很有可能是远远大于4。这样就造成,for里面的循环次数远远大于4次,因为,指针*pTemp本来指向一个4位的数组temp[4],现在由于pTemp++超过3次,已经不是指向temg[]这个4位数组了,而是大于temp[4]地址的地址了。
  我们知道,这个temp[]是一个局部变量,它应该保存在SRAM的数据堆栈里面,而数据堆栈是由高往低进栈的,紧挨着的就是全局变量区域,这个temp必定就是保存在离全局变量区域不远的数据堆栈里面。于是,只要for的次数够大,它不但改变了比temp后进的数据堆栈的数据,而且,跨过数据堆栈与全局变量区域的界限,直接修改了全局变量的某些值!
  其实,原来写这个程序的人,已经做个防止getIndex()大于2的处理,可是呢,悲剧的是length[]却少了一位数。呵呵,好在我究竟还是数了数它的位数(在实际的那个程序里,这个数组的位数自然不会是3位这么少,这样一眼就可以看出来少不少。)。不过,如果不是志刚的提示,让我提起心思去了解存储空间的问题,即使我数出来位数少了一位,也不一定知道问题的根本原因所在!

  其实,这里就是指针惹的祸。不怪人家说,指针是C、C++的灵魂啊——你知道,灵魂这东西,虽然有可能是个天使,也有可能是个恶魔。


沙发
mintspring| | 2016-3-22 23:24 | 只看该作者
指针的用法多种多样,功能十分强大又灵活,因此如果掌握不熟练,很容易用错。

使用特权

评论回复
板凳
734774645| | 2016-3-23 09:06 | 只看该作者
我一般采用数组代替指针,偶尔用一下取地址符号,取地址符号很不错,通过传递指针可以实现多参数的传递。

使用特权

评论回复
地板
玛尼玛尼哄| | 2016-3-23 20:05 | 只看该作者
定义类型不同,存储位置不同,取地址后,地址分配位置也不同

使用特权

评论回复
5
500days| | 2016-3-27 20:53 | 只看该作者
指针过界必须要避免,所以一定要详细阅读芯片资料

使用特权

评论回复
6
orangebanana| | 2016-3-28 20:57 | 只看该作者
我也遇到过这个问题,当时是写flash,把flash的地址弄错了,死活写不进去

使用特权

评论回复
7
gejigeji521|  楼主 | 2016-3-30 13:28 | 只看该作者
orangebanana 发表于 2016-3-28 20:57
我也遇到过这个问题,当时是写flash,把flash的地址弄错了,死活写不进去

刚才看到有个人还在问,为何他的写闪存,写100个没问题,写200个有问题,他把指针写出界了,根本没那么多的地址空间。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

180

主题

2250

帖子

8

粉丝