Вы неправильно поняли эти данные об использовании памяти. Ваш 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
access
, объявленная вmain()
, будет размещена в стеке. Вероятно, вы можете сказать это, посмотрев на сгенерированный ассемблерный код: пролог дляmain
, вероятно, уменьшитSP
примерно на 32 байта (31 для структуры и 1 для локальной переменнойr
(что не позволит вам получить доступ к глобальной переменной с тем же именем). !) Как утверждают @user3386109 и @kkrambo, данные во Flash инициализируются... они копируются в локальную (стековую) переменнуюaccess
где-то в началеmain
. - person Dave M.   schedule 29.05.2016.data
. Данные: ....data
. В первом случае должно быть.rodata
. - person Lundin   schedule 30.05.2016