【问题标题】:How does runtime memory allocation for local variable's work in embedded systems?局部变量的运行时内存分配如何在嵌入式系统中工作?
【发布时间】:2017-09-24 17:28:39
【问题描述】:

我们使用的是ccrx编译器,embOS RTOS 代码中有一个函数,

void fun( ) 
{
    if(condition) 
    { int a;}
    else if(condition1)
   {int b;}............ 
    else
   { int z;}
} 

每当函数被调用时,相关的线程堆栈就会溢出。如果注释了很少的 int 变量声明,则线程堆栈不会溢出。

栈是如何分配的?条件成功后不会分配内存吗?

【问题讨论】:

  • 我对 ccrx 编译器了解不多,但你肯定可以看一下反汇编列表,看看 abz 是否有条件地分配在 if 分支中或全部分配在函数的开头。
  • @David Grayson 感谢您的建议。我正在使用的系统没有任何调试器。
  • 反汇编不同于调试。你需要一个反汇编器或调试器。而且很多编译器不需要反汇编器就可以输出汇编。
  • @David Grayson 好的。我会检查
  • 调用函数时如何分配内存是非常系统特定的。无论函数如何执行,编译器都可能分配堆栈帧。或者它可能根本不使用堆栈,而是使用 CPU 寄存器。为了知道您的情况会发生什么,您必须阅读反汇编和/或编译器手册。不过,知道为什么堆栈溢出出现在这个特定的函数中可能并不是特别有趣。您首先要关注为什么会出现堆栈溢出。堆栈大小需要有更好的边距。

标签: c embedded rtos


【解决方案1】:

我们以 GCC 为例

void fun( int condition, int condition1 )
{
    if(condition)
    { int a; a=5;}
    else if(condition1)
   {int b; b=7;}
    else
   { int z; z=9; }
}

然后选择一个目标,而不是为了获得ccrx而付费或其他任何东西......

00000000 <fun>:
   0:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
   4:   e28db000    add r11, sp, #0
   8:   e24dd01c    sub sp, sp, #28
   c:   e50b0018    str r0, [r11, #-24] ; 0xffffffe8
  10:   e50b101c    str r1, [r11, #-28] ; 0xffffffe4
  14:   e51b3018    ldr r3, [r11, #-24] ; 0xffffffe8
  18:   e3530000    cmp r3, #0
  1c:   0a000002    beq 2c <fun+0x2c>
  20:   e3a03005    mov r3, #5
  24:   e50b3008    str r3, [r11, #-8]
  28:   ea000007    b   4c <fun+0x4c>
  2c:   e51b301c    ldr r3, [r11, #-28] ; 0xffffffe4
  30:   e3530000    cmp r3, #0
  34:   0a000002    beq 44 <fun+0x44>
  38:   e3a03007    mov r3, #7
  3c:   e50b300c    str r3, [r11, #-12]
  40:   ea000001    b   4c <fun+0x4c>
  44:   e3a03009    mov r3, #9
  48:   e50b3010    str r3, [r11, #-16]
  4c:   e1a00000    nop         ; (mov r0, r0)
  50:   e28bd000    add sp, r11, #0
  54:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
  58:   e12fff1e    bx  lr

没有分配

void fun( int condition, int condition1 )
{
    if(condition)
    { int a;/* a=5;*/}
    else if(condition1)
   {int b;/* b=7;*/}
    else
   { int z; /*z=9;*/ }
}

即使没有优化,这些变量也是死代码并已优化

00000000 <fun>:
   0:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
   4:   e28db000    add r11, sp, #0
   8:   e24dd00c    sub sp, sp, #12
   c:   e50b0008    str r0, [r11, #-8]
  10:   e50b100c    str r1, [r11, #-12]
  14:   e1a00000    nop         ; (mov r0, r0)
  18:   e28bd000    add sp, r11, #0
  1c:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
  20:   e12fff1e    bx  lr

堆栈上有一些字节用于对齐,它们可以剃掉一些并保持对齐,但这是另一个话题。

这里的重点是,仅仅因为在高级语言中你的变量只在函数的一部分中使用并不意味着编译器必须这样做,编译器当然是 gcc,倾向于完成所有的堆栈分配在函数的开头和最后的清理。就像这里所做的那样......

这没什么不同

int fun( void )
{
    static int x;
    x++;
    if(x>10) return(1);
    if(fun()) return(1);
    return(0);
}

给了

00000000 <fun>:
   0:   e59f2030    ldr r2, [pc, #48]   ; 38 <fun+0x38>
   4:   e5923000    ldr r3, [r2]
   8:   e2833001    add r3, r3, #1
   c:   e353000a    cmp r3, #10
  10:   e5823000    str r3, [r2]
  14:   da000001    ble 20 <fun+0x20>
  18:   e3a00001    mov r0, #1
  1c:   e12fff1e    bx  lr
  20:   e92d4010    push    {r4, lr}
  24:   ebfffffe    bl  0 <fun>
  28:   e2900000    adds    r0, r0, #0
  2c:   13a00001    movne   r0, #1
  30:   e8bd4010    pop {r4, lr}
  34:   e12fff1e    bx  lr
  38:   00000000    andeq   r0, r0, r0

Disassembly of section .bss:

00000000 <x.4089>:
   0:   00000000    andeq   r0, r0, r0

它是一个局部变量,但通过静态进入全局池,不像其他局部变量那样分配在堆栈上(或优化到寄存器中)。

虽然有趣的是,这是一个它没有立即在堆栈上分配的情况,虽然在这种情况下这很好,但如果你不需要递归,不要给堆栈增加负担。不错的优化。

没有理由假设堆栈指针会因为您在高级语言中所做的事情而在整个函数中多次更改。我的猜测是它使开发编译器更容易一次性完成所有工作,即使这会浪费内存。另一方面,你会花费更多的指令(​​空间和时间)。

【讨论】:

  • 我在一些 4.x 或 5.x 版本选项中添加了微弱的内存 gcc,以考虑 RAM 约束目标。这不仅取决于那些,还取决于整个代码。一个添加/删除的变量可以改变一切。当然,这也取决于平台架构和 ABI(寄存器数量、调用者/被调用者保存的寄存器等)。
  • 以上是 gcc 6.x,是的,随着时间的推移,编译器会更改 gcc、2.x、3.x 4.x。 5.x。 6.x 已针对相同目标(在本例中为 ARM)的先前版本进行了更改。当然,只需要一个变量或一行代码就可以将您推到边缘以用完寄存器,然后需要一个堆栈帧或使堆栈要求变得更糟。如果您经常调用该函数,那么它只会增加您的堆栈使用量。
  • 人们被教导认为全局变量是不好的,但在像 MCU 这样的内存非常受限的环境中,全局变量是在编译时分配的,你知道你的消耗量是多少。局部变量会因编译器和版本而异,今天采用工作的、发布的固件,明天进行一行修改,然后完全失败。所以你必须对每个发布的构建进行堆栈消耗分析。不只是查看 C 代码,还要查看编译器输出
  • 使用全局变量(如果我必须使用静态变量,我更喜欢使用 static 局部变量)可能不会真正改变某些架构上的问题。临时(匿名)对象仍可能导致堆栈使用量增加。解决这个问题的唯一方法也是分析堆栈使用情况,这在嵌入式系统上并不是一项简单的任务。
  • 刚刚检查:gcc 提供了很多选项来检查堆栈使用和布局。 -fstack-reuse 这个问题听起来很有趣。负面影响是:它使调试复杂,但在优化代码时这是正常的。这就是为什么在调试期间使用最大的设备(RAM/闪存/时钟)而不是更便宜、更小的目标设备是个好主意。
猜你喜欢
  • 2019-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-05
  • 2023-03-10
  • 2012-08-12
  • 2016-05-16
  • 2021-10-16
相关资源
最近更新 更多