Почему структура в C кажется размещенной только во флэш-памяти (ПЗУ)?

Я написал простой тестовый код и случайно обнаружил, что таблица поиска в структуре, похоже, размещена во Flash-памяти, и я не знаю, как это объяснить. Вот простой код:

#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? Я думаю, что во время выполнения таблицы поиска нет в этом стеке, я ошибаюсь? Почему структура не размещается в стеке?


person pop rock    schedule 29.05.2016    source источник
comment
Таблица поиска значения инициализации хранится во флэш-памяти. Они копируются в стек во время выполнения для инициализации структуры. Итак, структура находится в стеке (вы можете убедиться в этом, взглянув на ассемблерный код). Обратите внимание, что отчет об использовании памяти ничего не говорит вам об использовании стека.   -  person user3386109    schedule 29.05.2016
comment
Это справочная таблица. Вы не хотите, чтобы это было во флэш-памяти?   -  person Martin James    schedule 29.05.2016
comment
Ваша структура инициализируется. Значения инициализации должны храниться в энергонезависимой памяти, которая является флэш-памятью. Тогда программа никогда не изменяет содержимое структуры. Поэтому, возможно, компилятор понимает, что копия структуры в ОЗУ не нужна, и поэтому использует только копию во флэш-памяти.   -  person kkrambo    schedule 29.05.2016
comment
@Martin James Я не буду этого делать во Flash, но я запутался, потому что я не использовал макрос attribute, чтобы сообщить компилятору, где я хочу это!!   -  person pop rock    schedule 29.05.2016
comment
@kkrambo, так ты уверен, что я прав и компилятор не использует стек? я не уверен на 100%, что правильно понимаю вывод компилятора!   -  person pop rock    schedule 29.05.2016
comment
Я полагаю, что ваша структура access, объявленная в main(), будет размещена в стеке. Вероятно, вы можете сказать это, посмотрев на сгенерированный ассемблерный код: пролог для main, вероятно, уменьшит SP примерно на 32 байта (31 для структуры и 1 для локальной переменной r (что не позволит вам получить доступ к глобальной переменной с тем же именем). !) Как утверждают @user3386109 и @kkrambo, данные во Flash инициализируются... они копируются в локальную (стековую) переменную access где-то в начале main.   -  person Dave M.    schedule 29.05.2016
comment
Некоторые из этих комментариев на самом деле являются ответами - это не то, для чего нужны комментарии SO. Один из вас опубликует ответ! Или я возьму кредит.   -  person Clifford    schedule 30.05.2016
comment
В качестве примечания, как этот вывод памяти имеет смысл? Программа: ... .data. Данные: ... .data. В первом случае должно быть .rodata.   -  person Lundin    schedule 30.05.2016
comment
Пожалуйста, предоставьте код Ассемблера, сгенерированный компилятором. Несколько сомневаюсь, что есть какой-либо доступ к таблице (если предположить, что это минимально воспроизводимый пример. И откуда вы знаете он не находится в стеке? Для статического отпечатка ОЗУ это невозможно вывести.   -  person too honest for this site    schedule 31.05.2016


Ответы (2)


Список инициализаторов для структуры всегда будет во флэш-памяти (.rodata или .text), независимо от того, что вы делаете.

Сама структура является локальной областью действия, поэтому обычно она оказывается в ОЗУ. Однако компилятор, вероятно, заметил, что вы никогда не изменяете содержимое структуры, и поэтому полностью ее оптимизировал, получая значения непосредственно из флэш-памяти.

(Учитывая, что это какой-то 8-битный MCU, который, вероятно, не имеет состояний ожидания флэш-памяти, это было бы жизнеспособной оптимизацией.)

Чтобы поэкспериментировать, вы можете объявить структурную переменную volatile и посмотреть, увеличится ли использование ОЗУ.

Однако лучше всего объявить структуру как const. Если вам нужна модифицируемая копия в ОЗУ со всеми значениями, инициализированными по умолчанию, вы можете сделать так:

static const struct PEC table =  { ... };
...
struct PEC local = table;
person Lundin    schedule 30.05.2016

Вы неправильно поняли эти данные об использовании памяти. Ваш LUT был размещен в оперативной памяти, а не во флэш-памяти. Но поскольку она выделяется во время времени выполнения (это выделение стека), она не отображается в этом анализе использования памяти во время компиляции.

Тот факт, что он не выделен во флэш-памяти, не означает, что он не занимает место во флэш-памяти. На самом деле, это довольно расточительно для вспышки. Было бы эффективнее разместить его в разделе «данные» программы (т. е. и во флэш-памяти и в ОЗУ), и еще эффективнее иметь его только во флэш-памяти. Вы можете легко контролировать место, где размещается LUT, просто добавляя некоторые квалификаторы к переменной.

Распределение стека

В программе, как вы ее написали (без спецификаторов), LUT размещается в ОЗУ, в стеке, во время выполнения main(). Он не выделяется во flash. Что ж, содержимое явно должно быть где-то во флэш-памяти, но оно не хранится в виде массива. Вместо этого данные встраиваются в поток инструкций в виде инструкций 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+24, Y+25, ... являются адресами относительно кадра, указывающими на LUT в стеке.

Следует отметить, что, поскольку LUT определяется в области блока, вся эта инициализация повторяется на каждой итерации цикла. Кроме того, компилятор пытается оптимизировать все это, сохраняя как можно больше LUT во внутренних регистрах, чтобы убрать большую часть этих ldi из цикла. Но поскольку половина регистров ЦП (с 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, оптимизированная таким образом, использует три инструкции вместо двух, но только одна из них находится в цикле. Все это приводит к довольно расточительному расходу флэш-памяти с двумя-тремя 2-байтовыми инструкциями на запись LUT. Это также значительно замедляет итерации цикла. Если LUT перемещается в область действия функции (а не блока), инициализация выполняется только один раз, мы избавляемся от этих mov инструкций, и цикл больше не замедляется. Но все может быть еще эффективнее, если мы просто переместим LUT из стека.

Раздел данных

LUT можно переместить в раздел данных программы, сделав его глобальной переменной или, что более уместно, присвоив ей квалификатор static. В этом случае он выделяется во flash как массив, что гораздо эффективнее, чем встраивание тех же данных в поток инструкций (1 байт вместо 4–6 байт на запись). Раздел данных флэш-памяти копируется в раздел данных ОЗУ при запуске программы, до вызова main().

Только во флеше

Если LUT постоянен, его копирование в ОЗУ по-прежнему несколько расточительно. Компилятору можно дать указание хранить эти данные во флэш-памяти и обращаться к ним оттуда при необходимости, определив их как static __flash const. Квалификатор __flash не является стандартным C, но поддерживается gcc как «именованное адресное пространство», понятие, определенное в Расширения C для встраиваемых систем.

Однако это работает только в режиме C. При компиляции кода C++ приходится прибегать к еще менее стандартному и более примитивному PROGMEM., который требует, чтобы оператор нижнего индекса (array[index]) был заменен вызовом pgm_read_byte().

Единственным недостатком наличия постоянной LUT во флэш-памяти, а не в ОЗУ, является очень незначительное увеличение времени выполнения: чтение байта из флэш-памяти занимает 3 такта процессора, а не 3 такта процессора. 2 цикла для чтения ОЗУ.

Сравнение использования памяти

Я сделал несколько тестов, скомпилировав программу с помощью gcc 4.9.2 и различных комбинаций квалификаторов. Следует отметить, что const никоим образом не влияет на генерируемый код, но требуется, если используется __flash. static также требуется для __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

Столбец ОЗУ здесь относится только к ОЗУ, выделенному во время компиляции (данные + bss): он не включает использование стека. Если учитывать использование стека, только в последней строке таблицы будет показан небольшой объем оперативной памяти.

person Edgar Bonet    schedule 03.06.2016