AVR-GCC的编译器优化是个双刃剑,一不小心就被阴了~

题记:
      代码优化双刃剑,
      一不小心就出错。
      GCC里用static,
      出错之后难发觉,
      要想不覆前辙路,
      volatile来灭之!

      实验室开发的WSN结点使用AVR系列的单片机做的电路板,AVR-GCC提供了5级的代码优化,因为结点上跑的代码比较繁琐,需要完成找路由、组网、采集数据、传递数据、休眠、时间同步等功能,所以不开优化的话,代码基本上已经达到单片机容量的160%了。下面是Makefile文件中对代码优化的配置:
      # Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization.
      # (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
      OPT = s
      其中,OPT = s就表示开了s级的优化,这优化一开,编译出的hex文件马上从用掉单片机容量的160%降到接近于40%了。

      代码优化固然很棒,而且都是编译器帮你完成了,但是最近就碰到很头大的一件事:
      static BOOL timer2_flag;
      ISR(TIMER2_COMPA_vect)
      {
             timer2_flag = true;
      }
      int main(void)
      {
            ...
            timer2_flag = false;
            while( !timer2_flag );
            ...
      }
      
      这儿timer2的设置等代码没有列出来,本该在timer2中断来了之后将标志位timer2_flag置位掉,然后代码就跑出while循环的,可是timer2中断迟迟没来,调了一个下午,都没发现原因出在哪儿,因为一直怀疑中断的设置有错或者timer2定时器的预分频、中断类型等是否有设置错。

      困扰了一个下午的问题,后来只给代码加了1个关键字就没问题了,那就是“volatile”。
      把标志位的定义由static BOOL timer2_flag;改为:   static volatile BOOL timer2_flag;
      结果就达到了预期的目标。

      这是为什么呢?
      我清楚地记得,static与其字面上的静态、不变的概念是无关的,不可能因为加了这个关键字而导致timer2_flag就成了一个常量,因为static的真实用途应该只是限定该变量的使用范围不能跑出本文件以外而已。
      那么,为什么还会出这个问题呢?
      答案就出在优化上,虽然我希望的是一个能在中断中被修改的标志位,但,编译器则不这么认为,编译器把所有的修改timer2_flag的代码优化成了一句,当中断来的时候,其实可执行代码中并没有像预期一样地每次中断都执行一次timer2_flag的赋值;
      而volatile这个关键字则是传达给编译器这么一个信号:这个timer2_flag变量是多变的,经常会被修改的,请不要乱优化它,于是,这次编译器放过它,没把修改它的那句语句优化掉,程序自然就正常地跑了!

      编译器优化代码真是个双刃剑,而且,编译器总是没有程序员们想象的那么聪慧,它也会犯乱七八糟的很诡异的错,而编译器犯的错往往是最难察觉的,以后我得注意了~~

posted on 2009-09-03 21:35  涌远在调试  阅读(2153)  评论(0)    收藏  举报

导航