使用avr-gcc进行C ++的意外全局变量读取结果(本地变量访问是预期的)

时间:2013-04-16 18:44:07

标签: gcc compiler-construction global-variables avr avr-gcc

在avr-gcc 4.6.2中为ATmega328编译以下代码时,我得到了意外的全局变量读取结果:

#include <avr/io.h>
#include <util/delay.h>

#define LED_PORT            PORTD
#define LED_BIT             7
#define LED_DDR             DDRD

uint8_t latchingFlag;

int main() {
    LED_DDR = 0xFF;
    for (;;) {
        latchingFlag=1;
        if (latchingFlag==0) {
            LED_PORT ^= 1<<LED_BIT; // Toggle the LED
            _delay_ms(100);         // Delay
            latchingFlag = 1;
        }
    }
}

这是整个代码。我希望LED切换为永不执行,因为latchingFlag设置为1,但LED会持续闪烁。如果latchingFlag被声明为main()的本地,程序将按预期执行:LED永远不会闪烁。

反汇编的代码没有显示我能看到的任何陷阱,这里是使用全局变量对版本的主循环进行反汇编(延迟例程调用被注释掉;相同的行为)

  59                .L4:
  27:main.cpp      ****     for (;;) {
  60                    .loc 1 27 0
  61 0026 0000              nop
  62                .L3:
  28:main.cpp      ****         latchingFlag=1;
  63                    .loc 1 28 0
  64 0028 81E0              ldi r24,lo8(1)
  65 002a 8093 0000         sts latchingFlag,r24
  29:main.cpp      ****         if (latchingFlag==0) {
  66                    .loc 1 29 0
  67 002e 8091 0000         lds r24,latchingFlag
  68 0032 8823              tst r24
  69 0034 01F4              brne .L4
  30:main.cpp      ****             LED_PORT ^= 1<<LED_BIT; // Toggle the LED
  70                    .loc 1 30 0
  71 0036 8BE2              ldi r24,lo8(43)
  72 0038 90E0              ldi r25,hi8(43)
  73 003a 2BE2              ldi r18,lo8(43)
  74 003c 30E0              ldi r19,hi8(43)
  75 003e F901              movw r30,r18
  76 0040 3081              ld r19,Z
  77 0042 20E8              ldi r18,lo8(-128)
  78 0044 2327              eor r18,r19
  79 0046 FC01              movw r30,r24
  80 0048 2083              st Z,r18
  31:main.cpp      ****             latchingFlag = 1;
  81                    .loc 1 31 0
  82 004a 81E0              ldi r24,lo8(1)
  83 004c 8093 0000         sts latchingFlag,r24
  27:main.cpp      ****     for (;;) {
  84                    .loc 1 27 0
  85 0050 00C0              rjmp .L4

第71-80行负责端口访问:根据数据表,PORTD位于地址0x2B,即十进制43(参见第71-74行)。

latchingFlag变量的本地/全局声明之间的唯一区别是latchingFlag的访问方式:全局变量版本使用sts(直接存储到数据空间)和{{1 (直接从数据空间加载)访问lds,而本地变量版本使用latchingFlag(从数据空间加载间接注册)和ldd(将寄存器间接存储到数据)空格)使用寄存器std作为地址寄存器(可以用作堆栈指针,由avr-gcc AFAIK提供)。以下是反汇编的相关行:

Y

.bss部分中的全局版本也有 63 002c 8983 std Y+1,r24 65 002e 8981 ldd r24,Y+1 81 004a 8983 std Y+1,r24 。我真的不知道将不同的全局变量和局部变量行为归因于什么。这是avr-gcc命令行(注意latchingFlag):

-O0

使用/usr/local/avr/bin/avr-gcc \ -I. -g -mmcu=atmega328p -O0 \ -fpack-struct \ -fshort-enums \ -funsigned-bitfields \ -funsigned-char \ -D CLOCK_SRC=8000000UL \ -D CLOCK_PRESCALE=8UL \ -D F_CPU="(CLOCK_SRC/CLOCK_PRESCALE)" \ -Wall \ -ffunction-sections \ -fdata-sections \ -fno-exceptions \ -Wa,-ahlms=obj/main.lst \ -Wno-uninitialized \ -c main.cpp -o obj/main.o 编译器标志,循环从反汇编中消失,但如果-Os被声明为latchingFlag,则可以强制再次出现,在这种情况下意外持续存在。

3 个答案:

答案 0 :(得分:2)

根据您的反汇编程序列表,latchingFlag全局变量位于RAM地址0.此地址对应于镜像寄存器r0,并且不是全局变量的有效RAM地址。

答案 1 :(得分:1)

在EE聊天中进行了几次检查和代码比较后,我注意到我的avr-gcc版本(4.7.0)将latchFlag的值存储在0x0100中,而Egor Skriptunoff提到了SRAM addres 0在OP的汇编列表中。

看看OP的反汇编(avr-dump版本),我注意到OP的编译器(4.6.2)将latchFlag值存储在与我的编译器不同的地址(特别是0x060)中(版本) 4.7.0),将latchFlag值存储在地址0x0100

我的建议是将avr-gcc版本更新到至少版本4.7.0。 4.7.0的优势而不是最新和最大可用的是再次比较生成的代码的能力我的发现。

当然,如果4.7.0解决了这个问题,那么升级到更新版本(如果有的话)会有害。

答案 2 :(得分:1)

Egor Skriptunoff建议几乎完全正确:SRAM变量映射到错误的内存地址。 latchingFlag变量不在0x0100地址,这是第一个有效的SRAM地址,但映射到0x060,与WDTCSR寄存器重叠。这可以在反汇编行中看到,如下所示:

lds r24, 0x0060

该行应该从SRAM加载latchingFlag的值,我们可以看到使用位置0x060而不是0x100

问题必须与bug in the binutils满足两个条件:

  • 使用--gc-sections标志调用链接器(编译器选项:-Wl,--gc-sectionsto save code space
  • 您的SRAM变量均未初始化(即初始化为非零值)

当满足这两个条件时,.data部分将被删除。缺少.data部分时,SRAM变量从地址0x060开始,而不是0x100

一种解决方案是重新安装binutils:当前版本修复了此错误。另一种解决方案是编辑链接器脚本:在Ubuntu上,这可能在/usr/lib/ldscripts。对于ATmega168 / 328,需要编辑的脚本是avr5.x,但您应该真正编辑它们,否则您可能会在其他AVR平台上遇到此错误。需要进行的更改如下:

   .data   : AT (ADDR (.text) + SIZEOF (.text))
   {
      PROVIDE (__data_start = .) ;
-     *(.data)
+     KEEP(*(.data))

所以用*(.data)替换行KEEP(*(.data))。这可确保不丢弃.data部分,因此SRAM变量地址从0x0100开始