意外的全局变量读取导致C++;将avr gcc用于(本地变量访问按预期进行)

意外的全局变量读取导致C++;将avr gcc用于(本地变量访问按预期进行),gcc,compiler-construction,global-variables,avr,avr-gcc,Gcc,Compiler Construction,Global Variables,Avr,Avr Gcc,在ATmega328的avr gcc 4.6.2中编译以下代码时,我得到了意外的全局变量读取结果: #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_D

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

#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;
        }
    }
}
第71-80行负责端口访问:根据数据表,
PORTD
位于地址
0x2B
,即十进制
43
(参见第71-74行)

latchingFlag
变量的本地/全局声明之间的唯一区别是如何访问
latchingFlag
:全局变量版本使用
sts
(直接存储到数据空间)和
lds
(直接从数据空间加载)访问
latchingFlag
,而局部变量版本使用
ldd
(从数据空间间接加载到寄存器)和
std
(从寄存器间接存储到数据空间),使用寄存器
Y
作为地址寄存器(avr gcc AFAIK可将其用作堆栈指针)。以下是拆解中的相关行:

  63 002c 8983              std Y+1,r24

  65 002e 8981              ldd r24,Y+1

  81 004a 8983              std Y+1,r24
全局版本在.bss部分中也有
latchingFlag
。我真的不知道将不同的全局变量和局部变量行为归因于什么。下面是AVRCC命令行(注意
-O0
):


使用
-Os
编译器标记,循环从反汇编中消失,但如果声明
latchingFlag
为volatile,则可以再次强制循环,在这种情况下,我仍然会遇到意外情况。

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

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

在EE聊天中经过几次检查和代码比较后,我注意到我的avr gcc(4.7.0)版本将
LATCFLAG
的值存储在
0x0100
中,鉴于上述SRAM地址
0
在OP的汇编列表中

查看OP的反汇编(avr转储版本),我注意到OP的编译器(4.6.2)将
latchFlag
值存储在不同的地址(具体地说,
0x060
),而我的编译器(4.7.0版)将
latchFlag
值存储在
0x0100
地址

我的建议是将avr gcc版本至少更新为4.7.0版本。4.7.0而不是最新的和最好的可用版本的优点是能够再次将生成的代码与我的发现进行比较


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

在EE聊天中经过几次检查和代码比较后,我注意到我的avr gcc版本(4.7.0)在
0x0100
中存储了
latchFlag
的值,鉴于上述SRAM地址
0
在OP的汇编列表中

查看OP的反汇编(avr转储版本),我注意到OP的编译器(4.6.2)将
latchFlag
值存储在不同的地址(具体地说,
0x060
),而我的编译器(4.7.0版)将
latchFlag
值存储在
0x0100
地址

我的建议是将avr gcc版本至少更新为4.7.0版本。4.7.0而不是最新的和最好的可用版本的优点是能够再次将生成的代码与我的发现进行比较

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

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

lds r24, 0x0060
   .data   : AT (ADDR (.text) + SIZEOF (.text))
   {
      PROVIDE (__data_start = .) ;
-     *(.data)
+     KEEP(*(.data))
此行应该从SRAM加载
latchingFlag
的值,我们可以看到使用了位置
0x060
而不是
0x100

问题必须在满足两个条件的情况下解决:

  • 使用
    --gc sections
    标志调用链接器(编译器选项:
    -Wl,--gc sections
  • 所有SRAM变量均未初始化(即初始化为非零值)
当这两个条件都满足时,
.data
部分将被删除。当
.data
部分缺失时,SRAM变量从地址
0x060
开始,而不是
0x100

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

lds r24, 0x0060
   .data   : AT (ADDR (.text) + SIZEOF (.text))
   {
      PROVIDE (__data_start = .) ;
-     *(.data)
+     KEEP(*(.data))
因此,将行
*(.data)
替换为
KEEP(*.data))
。这确保了
.data
部分不会被丢弃,因此SRAM变量地址从
0x0100

开始的建议几乎完全正确:SRAM变量映射到错误的内存地址。
latchingFlag
变量不在
0x0100
地址,这是第一个有效的SRAM地址,但映射到
0x060
,与
WDTCSR
寄存器重叠。这可以在如下拆卸线中看到:

lds r24, 0x0060
   .data   : AT (ADDR (.text) + SIZEOF (.text))
   {
      PROVIDE (__data_start = .) ;
-     *(.data)
+     KEEP(*(.data))
此行应该从SRAM加载
latchingFlag
的值,我们可以看到使用了位置
0x060
而不是
0x100<