【问题标题】:AVR C compilers behavior. Memory managementAVR C 编译器行为。内存管理
【发布时间】:2018-02-01 19:24:28
【问题描述】:

AVR C 编译器是否让程序记住 SRAM 中函数开始将其数据(变量、数组)存储在数据堆栈中的索引寄存器之一的地址,以便通过公式获得局部变量的绝对地址:

absoluteAdr = functionDataStartAdr + localShiftOfVariable.

当变量声明的长度或堆栈指针在函数的结束/开始增加所有变量长度时,它们是否会增加数据堆栈点。

【问题讨论】:

  • 你尝试的时候发生了什么?你的编译器做了什么?
  • 这就是我的意思,去试试吧,我假设你已经有 gcc 或其他免费/试用版之一......
  • 我们知道答案是肯定的并且知道,你的问题有点含糊,我理解语言障碍,这很好。显然编译器必须生成可以寻址/访问变量的代码。有时变量被优化为寄存器,有时是地址。有时它是 pc 相对的,有时它是绝对的。并且与架构提供的内容以及编译器作者选择的内容有很大关系。
  • 当项目在堆栈上时,它通常是通过引用,特别是对于 C,堆栈指针或带有一些偏移量的帧指针,所以是的,基数加偏移量提供了绝对值。 pc relative 是 pc 加上一个偏移量提供了一个绝对值。对于全局变量或静态局部变量,编译器通常会留下一个 pc 相对位置(这里一般来说,根据需要将其应用于 AVR 或其他),链接器稍后会用绝对地址填充该相对位置。如果您提供与位置无关的代码 (PIC),那么您会玩更多游戏
  • 有时一个变量甚至会在一个有地址的寄存器中结束(这可能会更令人困惑) - AVR gcc 有点特殊,因为它似乎将寄存器作为寄存器和内存来寻址(在 AVR 寄存器文件中)

标签: c compiler-construction microcontroller avr ram


【解决方案1】:

让我们看一下avr-gcc,它可以免费获得,包括its ABI

AVR C 编译器是否让程序记住 SRAM 中函数开始将其数据(变量、数组)存储在数据堆栈中的索引寄存器之一的地址,以便通过公式获得局部变量的绝对地址:

是的,不,这取决于:

静态存储

对于静态存储中的变量,即定义的变量

unsigned char func (void)
{
    static unsigned char var;
    return ++var;
}

编译器生成一个类似var.123 的符号,具有适当的大小(在这种情况下为1 个字节)。然后链接器/定位器将分配地址。

func:
    lds  r24,var.1505
    subi r24,lo8(-(1))
    sts  var.1505,r24
    ret
    .local  var.1505
    .comm   var.1505,1,1

自动

如果可能,自动变量会保存在寄存器中,否则编译器会在函数的框架中分配空间。甚至可能会出现变量被优化出来的情况,在这种情况下它们在程序中的任何地方都不存在:

int add (void)
{
    int a = 1;
    int b = 2;
    return a + b;
}

add:
    ldi r24,lo8(3)
    ldi r25,0
    ret

有 3 种类型的实体存储在函数的框架中,所有这些实体都可能存在或不存在,具体取决于程序:

  • 被调用者保存的寄存器由函数序言保存(PUSH'ed)并由 Epilogue 恢复(POP'ed)。当局部变量被分配给被调用者保存的寄存器时,这是需要的。

  • 不能分配给寄存器的局部变量的空间。当变量太大而无法保存在寄存器中,自动变量太多,或者变量的地址被占用(并且无法优化获取地址)时,就会发生这种情况。这是因为您无法获取寄存器1 的地址。

    void use_address (int*);
    
    void func (void)
    {
       int a;
       use_address (&a);
    }
    

    这些变量的空间在序言中分配,在尾声中释放。未实现收缩包装:

    func:
        push r28
        push r29
        rcall .
        in r28,__SP_L__
        in r29,__SP_H__
        /* prologue: function */
        /* frame size = 2 */
        /* stack size = 4 */
        movw r24,r28
        adiw r24,1
        rcall use_address
        pop __tmp_reg__
        pop __tmp_reg__
        pop r29
        pop r28
        ret
    

    在本例中,a 占用了由rcall . 分配的 2 个字节(它是为具有 16 位程序计数器的设备编译的)。然后编译器用堆栈指针的值初始化帧指针Y (R29:R28)。这是必需的,因为在 AVR 上,您无法通过 SP 访问内存;唯一涉及 SP 的内存操作是 PUSHPOP。然后该变量的地址Y+1 被传递到R24。函数调用后,结语释放帧并恢复R28和R29。

  • 必须在堆栈上传递的参数:

    void xfunc (int, ...);
    
    void call_xfunc (void)
    {
        xfunc (42);
    }
    

    这些参数被压入,被调用者从堆栈中取出它们。这些参数在调用周围推送/弹出,但也可以通过-maccumulate-args 累积。

    call_func:
        push __zero_reg__
        ldi r24,lo8(42)
        push r24
        rcall xfunc
        pop __tmp_reg__
        pop __tmp_reg__
        ret
    

    在此示例中,参数必须在堆栈上传递,因为 ABI 规定 varargs 函数的所有参数都必须在堆栈上传递,包括命名的参数。

有关框架布局和参数传递方式的描述,请参阅[框架布局和参数传递] (https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout)。

1 一些 AVR 实际上允许这样做,但你永远不会(比如在 NEVER 中)想要传递通用寄存器的地址!

【讨论】:

    【解决方案2】:

    编译器不管理 RAM,编译器在编译时计算每个数据部分(如 bss、数据、文本、rodata 等)所需的大小,并为每个翻译单元生成可重定位的目标文件

    链接器随后生成一个目标文件并将可重定位地址分配给根据链接器配置文件LCF映射的绝对地址。

    在运行时,机制取决于架构本身。通常,每个函数调用在堆栈中都有一个框架,其中定义了参数、返回地址和局部变量。堆栈随着变量的创建而扩展,对于低成本的 AVR 微控制器,没有关于堆栈增加或堆栈与另一个内存部分(通常是堆)之间的重叠的内存管理保护。即使有 OS 管理对超出其分配堆栈的任务的保护,但没有内存管理单元,OS 所能做的就是断言具有非法内存访问原因的 RESET

    【讨论】:

    • 这个答案在一些重要细节上是不正确的——例如,即使在存在这种区别的环境中,链接器也不会“将虚拟地址分配给物理地址”。 (AVR 根本不使用虚拟寻址。)此外,您似乎没有回答这个问题。
    • 我的意思是虚拟寻址是编译器不会为数据段分配物理绝对地址,它是链接器的工作,链接器负责根据 LCF 为不同的数据段设置绝对地址,见下文[链接]blog.feabhas.com/2012/06/the-c-build-process
    • 这不是术语“虚拟寻址”的意思。该术语用于参考使用 MMU 实现虚拟内存的系统。 AVR 不是这样的系统。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    相关资源
    最近更新 更多