Как стековая память используется для функций и локальных переменных?

8

Я хотел сохранить некоторые значения в EEPROM, а также хотел освободить SRAM, избегая некоторых объявлений переменных, но память EEPROM является байтовой.

Если я хочу сохранить значение типа int, мне придется использовать некоторые выражения несколько раз. Я думал, что сделаю некоторые функции для них. Но я обеспокоен тем, что, если я создам функцию, она все равно будет занимать память SRAM, лучше я объявляю переменную int вместо использования EEPROM.

Как функции и локальные переменные хранятся в SRAM? Хранит ли он только адрес указателя функции из флэш-памяти или все переменные и команды хранятся в стеке?

Нафис
источник
4
Помните, что EEPROM доступна для записи только ограниченное количество раз, чтение ее неограниченно. В соответствии с таблицей AVR EEPROM имеет только 100000 циклов, что звучит так же много, но когда вы пытаетесь использовать его как SRAM, оно будет длиться довольно короткий период.
Джиппи
О, МОЙ БОГ! После этого будет ли EEPROM бесполезным? Я собираюсь проверить таблицу!
Нафис
Флэш-память также имеет жизненный стиль. Разумнее не много записывать программу.
Нафис
При обычном использовании числа, указанные для флэш-памяти и EEPROM, не представляют никаких проблем. Уравнение меняется, когда вы начинаете использовать его, как вы используете SRAM.
Джиппи

Ответы:

4

В стеке хранятся только данные функции; его код остается во флэш-памяти. Вы не можете реально уменьшить использование SRAM, используя вместо этого EEPROM, потому что, как вы видели, EEPROM не адресуется таким же образом. Код для чтения и хранения EEPROM также должен использовать SRAM - возможно, столько же SRAM, сколько вы пытались сохранить! ЭСППЗУ также медленно записывается и имеет ограниченное время жизни (по количеству записей в каждый байт), что делает нецелесообразным использование для хранения вида временных данных, которые мы обычно помещаем в стек. Он лучше подходит для сохранения редко изменяемых данных, таких как уникальная конфигурация устройства для серийных устройств, или для регистрации нечастых ошибок для последующего анализа.

Отредактировано: нет стека для этой функции, пока функция не была вызвана, так что да, то есть, когда туда попадают какие-либо данные функции. Что происходит после возврата функции, так это то, что ее стековый кадр (зарезервированная область SRAM) больше не зарезервирован. В конечном итоге он будет повторно использован другим вызовом функции. Вот диаграмма стека C в памяти. Когда кадр стека больше не используется, он просто освобождается, и его память становится доступной для повторного использования.

JRobert
источник
Я так думаю, когда функция вызывается, только тогда данные внутри нее сохраняются в стеке. После выполнения функции данные стираются из стека / SRAM. Я прав?
Нафис
5

Локальные переменные и параметры функции хранятся в стеке. Однако это не причина, чтобы не использовать их. Компьютеры предназначены для такой работы.

Память стека используется только при активной функции. Как только функция возвращается, память освобождается. Память стека - ХОРОШАЯ вещь.

Вы не хотите использовать рекурсивные функции с большим количеством уровней рекурсии или размещать много больших структур в стеке. Нормальное использование в порядке однако.

Стек 6502 занимает всего 256 байтов, но Apple II работает просто отлично.

Дункан С
источник
Итак, вы имеете в виду, что функция будет сохранена со всеми своими локальными переменными, параметрами и выражениями в стек временно, только когда она вызывается? Иначе оно останется в программе / флеш-памяти? Будет ли он после выполнения стерт из стека? Я говорил об Arduino на самом деле, поскольку это Arduino Forum, я не упомянул об этом.
Нафис
Нет, в стеке находятся только параметры функции и локальные переменные. Код функции не сохраняется в стеке. Не думай об этом.
Дункан C
5

AVR (семейство микроконтроллеров, традиционно используемое на платах Arduino) - это Гарвардская архитектура , означающая, что исполняемый код и переменные находятся в двух отдельных запоминающих устройствах - в данном случае флэш-память и SRAM. Исполняемый код никогда не покидает флэш-память.

Когда вы вызываете функцию, адрес возврата обычно помещается в стек. Исключение составляют случаи, когда вызов функции происходит в конце вызывающей функции. В этом случае будет использоваться адрес возврата функции, вызвавшей вызывающую функцию - он уже находится в стеке.
Помещаются ли какие-либо другие данные в стек, зависит от давления регистра в вызывающей функции и вызываемой функции. Регистры являются рабочей областью ЦП, AVR имеет 32 однобайтовых регистра. Доступ к регистрам может осуществляться напрямую с помощью инструкций ЦП, тогда как данные в SRAM сначала должны быть сохранены в регистрах. Только если аргументы или локальная переменная слишком велики или слишком велики, чтобы поместиться в регистры, они будут помещены в стек. Однако структуры всегда хранятся в стеке.

Подробнее о том, как стек используется компилятором GCC на платформе AVR, можно прочитать здесь: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout.
Прочитать разделы «Расположение кадра» и «Соглашение о вызовах». ,

user2973
источник
1

Сразу после вызова функции, входящей в функцию, первый код, который выполняется, должен уменьшить указатель стека на величину, равную пространству, требуемому для временных переменных, внутренних для функции. Самое замечательное в этом то, что все функции становятся реентерабельными и рекурсивными, потому что их переменные построены на стеке вызывающей программы. Это означает, что если прерывание останавливает выполнение одной программы и передает выполнение другой, оно также может вызывать ту же функцию, не мешая друг другу.

Пол Дент
источник
1

Я изо всех сил пытался создать пример кода, чтобы продемонстрировать то, что здесь говорят отличные ответы, но пока безуспешно. Причина в том, что компилятор агрессивно оптимизирует вещи. До сих пор мои тесты вообще не использовали стек, даже с локальными переменными в функции. Причины:


  • Компилятор может рядный вызов функции, таким образом, обратный адрес не может быть в стек на всех. Пример:

    void foo (byte a) { digitalWrite (13, a); } void loop () { foo (5); }

    Компилятор превращает это в:

    void loop () { digitalWrite (13, 5); }

    Нет вызова функции, не используется стек.


  • Компилятор может передавать аргументы в регистрах , тем самым сохраняя его, помещая их в стек. Пример:

    digitalWrite (13, 1);

    Компилируется в:

    158: 8d e0 ldi r24, 0x0D ; 13 15a: 61 e0 ldi r22, 0x01 ; 1 15c: 0e 94 05 01 call 0x20a ; 0x20a <digitalWrite>

    Аргументы помещаются в регистры и, таким образом, стек не используется (кроме адреса возврата для вызова digitalWrite).


  • Локальные переменные вполне могут быть помещены в регистры, что опять же избавляет от необходимости использовать оперативную память. Это не только экономит оперативную память, но и быстрее.

  • Компилятор оптимизирует переменные, которые вы не используете. Пример:

    void foo (byte a) { unsigned long bar [100]; bar [1] = a; digitalWrite (9, bar [1]); } void loop () { foo (3); } // end of loop

    Теперь, когда есть выделить 400 байт для «бар» , не так ли? Нет:

    00000100 <_Z3fooh>: 100: 68 2f mov r22, r24 102: 89 e0 ldi r24, 0x09 ; 9 104: 0e 94 cd 00 call 0x19a ; 0x19a <digitalWrite> 108: 08 95 ret 0000010a <loop>: 10a: 83 e0 ldi r24, 0x03 ; 3 10c: 0e 94 80 00 call 0x100 ; 0x100 <_Z3fooh> 110: 08 95 ret

    Компилятор оптимизировал весь массив ! Это может сказать, что мы действительно просто делаем digitalWrite (9, 3)и это то, что он генерирует.


Мораль истории: не пытайтесь перехитрить компилятор.

Ник Гаммон
источник
Большинство нетривиальных функций используют стек для сохранения некоторых регистров, чтобы их можно было использовать для хранения локальных переменных. Затем возникает забавная ситуация, когда в стеке фрейма функции содержатся локальные переменные, принадлежащие ее вызывающей стороне .
Эдгар Бонет