【问题标题】:Why a struct in C seems to be allocated only in Flash(ROM) memory?为什么 C 中的结构似乎只在 Flash(ROM) 内存中分配?
【发布时间】:2016-05-29 17:48:28
【问题描述】:

我写了一个简单的测试代码,我无意中发现结构中的查找表似乎分配在闪存中,我不知道如何解释。这是简单的代码:

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

struct PEC {
    uint8_t lookup[31] ;
};

uint8_t r;

int main() {

uint8_t r = rand() % 20;

    while(1) {

    struct PEC access = {
0x00U, 0x07U, 0x0EU, 0x09U, 0x1CU, 0x1BU, 0x12U, 0x15U,
0x38U, 0x3FU, 0x36U, 0x31U, 0x24U, 0x23U, 0x2AU, 0x2DU,
0x70U, 0x77U, 0x7EU, 0x79U, 0x6CU, 0x6BU, 0x62U, 0x65U,
0x48U, 0x4FU, 0x46U, 0x41U, 0x54U, 0x53U} ;

volatile uint8_t v = access.lookup[r] ;
_delay_ms(1500);
    }
}

编译器有输出:

AVR 内存使用情况

设备:atmega168

程序:870 字节(5.3% 已满) (.text + .data + .bootloader)

数据:5 个字节(0.5% 已满) (.data + .bss + .noinit)

如果分配是在 Flash 中,您能解释一下吗? 我认为在执行期间查找表不在那个堆栈上,我错了吗? 为什么没有在 Stack 上分配结构?

【问题讨论】:

  • 查找表初始化值存储在闪存中。它们在运行时被复制到堆栈以初始化结构。所以结构 is 在堆栈上(您可以通过查看汇编代码来确认这一点)。请注意,内存使用情况报告不会告诉您任何有关堆栈使用情况的信息。
  • 这是一个查找表。你不希望它在 Flash 中?
  • 你的结构被初始化了。初始化值必须存储在非易失性存储器中,即闪存。然后程序永远不会修改结构内容。所以也许编译器意识到结构的 RAM 副本不是必需的,因此它只使用闪存副本。
  • @Martin James 我不会在 Flash 中这样做,但我很困惑,因为我没有使用 attribute 宏来告诉编译器我想要它的位置!!
  • 我相信您在main() 中声明的access 结构将被分配到堆栈上。您可能可以通过查看生成的汇编代码来判断:main 的序言可能会将 SP 减少 32 个字节左右(结构体为 31,局部变量 r 为 1(这将阻止您访问同名的全局变量!)作为@user3386109 和@kkrambo 状态,Flash 中的数据正在初始化...它被复制到本地(堆栈)变量access main 开头的某处。

标签: c embedded avr


【解决方案1】:

无论你做什么,结构的初始化列表总是在闪存中(.rodata 或 .text)。

结构本身是本地范围,所以它通常会在 RAM 中结束。但是,编译器可能注意到您从不修改结构的内容,因此完全优化它,直接从闪存中获取值。

(鉴于这是一些可能没有闪存等待状态的 8 位 MCU,这将是一个可行的优化。)

为了进行实验,您可以声明结构变量 volatile 并查看是否可以增加 RAM 使用率。

然而,最好的做法是将结构声明为const。如果您需要它在 RAM 中的可修改副本,并将所有值初始化为默认值,您可以这样做:

static const struct PEC table =  { ... };
...
struct PEC local = table;

【讨论】:

    【解决方案2】:

    您误解了内存使用数据。您的 LUT 分配在 RAM,不在闪存中。但由于它是在 运行时 期间分配的(它是 堆栈分配),它不会显示在此编译时间内存使用情况中 分析。

    在flash中没有分配不代表不吃 闪存空间。事实上,它非常浪费闪存。这将是 在程序的“数据”部分中分配它更有效(即 都在闪存中在 RAM 中),但在闪存中更有效 只要。您可以轻松控制分配LUT的位置 通过向变量添加一些限定符。

    堆栈分配

    使用您编写的程序(无限定符),分配 LUT 在 RAM 中,在堆栈中,在main() 的执行期间。它是不是 在闪存中分配。那么,内容显然必须在某处 闪存,但它不存储为数组。相反,数据是 以ldi的形式嵌入指令流中(立即加载) 指示。这些指令加载一个具有常数值的寄存器 包含在指令本身中。这里是摘录的 main()的反汇编:

    ldi r24, 0x65  ; load register r24 with the constant 0x65
    std Y+24, r24  ; store r24 in the stack
    ldi r24, 0x48  ; load r24 with the constant 0x48
    std Y+25, r24  ; store r24 in the stack
    ...etc...
    

    这里,Y用作帧指针,而Y+24Y+25,...是 指向堆栈中 LUT 的帧相关地址。

    需要注意的是,由于 LUT 是在块范围内定义的,所以这 在循环的每次迭代中重复整个初始化。还, 编译器试图通过保留尽可能多的 LUT 来优化所有这些 可能在内部寄存器中,以便将其中大部分 ldi 移出 的循环。但是由于一半的 CPU 寄存器(r0 到 r15)是 不支持 ldi,这需要额外的“移动”指令:

    before the loop:
        ...
        ldi r18, 0x07  ; load r18 with the constant 0x07
        mov r3, r18    ; copy r18 into r3
        ldi r19, 0x0E  ; load r19 with the constant 0x0E
        mov r4, r19    ; copy r19 into r4
        ...
    
    inside the loop:
        ...
        std Y+2, r3  ; store r3 in the stack
        std Y+3, r4  ; store r4 in the stack
        ...
    

    以这种方式优化的每个 LUT 条目都使用三个指令,而不是 两个,但只有一个在循环中。这一切最终都变得相当 浪费闪存,每个 LUT 条目有两到三个 2 字节指令。 它还大大减慢了循环迭代。如果 LUT 移动到 函数(而不是块)范围,初始化只进行一次, 我们摆脱了那些mov 指令,并且循环没有减慢 了。但是,如果我们简单地移动 LUT,事情会更加高效 出栈。

    数据部分

    LUT 可以移动到程序的数据部分,方法是使其成为 全局变量,或者——也许更恰当地——通过给它 static 限定符。在这种情况下,它在闪存中分配作为 数组,这比嵌入相同数据效率更高 在指令流中(每个条目 1 个字节而不是 4-6 个字节)。这 闪存的数据部分被复制到 RAM 的数据部分 程序启动,在调用main()之前。

    仅在 Flash 中

    如果 LUT 是恒定的,将其复制到 RAM 仍然有点 浪费。可以指示编译器将该数据保存在闪存中 – 并在需要时从那里访问它 – 通过将其限定为 static __flash const__flash 限定符不是标准 C,而是 它被 gcc 支持为“命名地址空间”,定义在 C extensions for embedded systems

    这仅适用于 C 模式。如果编译 C++ 代码,必须求助于 到更不标准和更原始的 PROGMEM 属性,要求下标运算符 (array[index]) 为 替换为对pgm_read_byte() 的调用。

    在闪存而不是 RAM 中使用恒定 LUT 的唯一缺点是 执行时间略微增加:从闪存读取一个字节需要 3 个 CPU 周期,v.s.读取 RAM 需要 2 个周期。

    内存使用比较

    我做了一些测试,用 gcc 4.9.2 和各种编译程序 限定词的组合。需要注意的是const 没有 以最轻微的方式影响生成的代码,但如果 使用__flashstatic 也需要 __flash,除了 全局变量。

    flash   RAM    qualifiers             scope
    ──────────────────────────────────────────────
     866      5    NONE, const            block
     826      5    NONE, const            function
     722     37    static, static const
     722      5    static __flash const
    

    此处的 RAM 列仅指编译时分配的 RAM time (data + bss):不包括堆栈使用。如果堆栈使用是 算上,只有表格的最后一行会显示一个小 RAM 足迹。

    【讨论】:

      猜你喜欢
      • 2013-06-05
      • 1970-01-01
      • 1970-01-01
      • 2015-10-08
      • 2014-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多