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


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 в памяти. Когда кадр стека больше не используется, он просто освобождается, и его память становится доступной для повторного использования.


Я так думаю, когда функция вызывается, только тогда данные внутри нее сохраняются в стеке. После выполнения функции данные стираются из стека / 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.
Прочитать разделы «Расположение кадра» и «Соглашение о вызовах». ,


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)и это то, что он генерирует.


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


Большинство нетривиальных функций используют стек для сохранения некоторых регистров, чтобы их можно было использовать для хранения локальных переменных. Затем возникает забавная ситуация, когда в стеке фрейма функции содержатся локальные переменные, принадлежащие ее вызывающей стороне .
— Эдгар Бонет
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.