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