Как сохранить пространство стека с хорошим дизайном? - PullRequest
12 голосов
/ 25 сентября 2008

Я программирую на C для встроенного микроконтроллера с ограниченной оперативной памятью и ОСРВ.

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

Есть ли альтернатива, чтобы код был хорошо организован и читабелен, сохраняя при этом память?

Ответы [ 10 ]

11 голосов
/ 25 сентября 2008

Попытайтесь сделать стек вызовов более плоским, поэтому вместо a() вызов b(), который вызывает c(), который вызывает d(), имеет a() вызов b(), c() и d(). .

Если на функцию ссылаются только один раз, отметьте ее inline (при условии, что ваш компилятор поддерживает это).

10 голосов
/ 27 сентября 2008

В вашем стеке есть 3 компонента:

  • Адрес возврата вызова функции
  • Параметры вызова функции
  • автоматические (локальные) переменные

Ключом к минимизации использования стека является минимизация передачи параметров и автоматических переменных. Потребление места для самого вызова функции довольно минимально.

Параметры

Одним из способов решения проблемы параметров является передача структуры (через указатель) вместо большого количества параметров.


foo(int a, int b, int c, int d)
{
...
   bar(int a, int b);
}

сделать это вместо:


struct my_params {
   int a;
   int b;
   int c;
   int d;
};
foo(struct my_params* p)
{
   ...
   bar(p);
};

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

Автоматические переменные (локальные)

Это, как правило, самый большой потребитель стекового пространства.

  • Массивы - убийца. Не определяйте массивы в ваших локальных функциях!
  • Минимизировать количество локальных переменных.
  • Используйте наименьший необходимый тип.
  • Если повторный вход не является проблемой, вы можете использовать статические переменные модуля.

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

Некоторые RTOS поддерживают локальное хранилище потоков, которое выделяет «глобальное» хранилище для каждого потока. Это может позволить вам иметь несколько независимых глобальных переменных для каждой задачи, но это сделает ваш код не таким простым.

6 голосов
/ 25 сентября 2008

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

В C все переменные, объявленные внутри функции, «автоматически управляются», что означает, что они размещены в стеке.

Определение объявлений как «статических» сохраняет их в основной памяти, а не в стеке. Они в основном ведут себя как глобальные переменные, но все же позволяют вам избежать вредных привычек, связанных с чрезмерным использованием глобальных переменных. Вы можете обосновать объявление больших долговременных буферов / переменных статическими, чтобы уменьшить нагрузку на стек.

Имейте в виду, что это не работает / совсем не работает, если ваше приложение является многопоточным или вы используете рекурсию.

5 голосов
/ 25 сентября 2008

Включите оптимизацию, особенно агрессивное встраивание. Компилятор должен иметь возможность встроенных методов для минимизации вызовов. В зависимости от используемого вами компилятора и параметров оптимизации, пометка некоторых методов как inline может помочь (или может быть проигнорирована).

В GCC попробуйте добавить флаг "-finline-functions" (или -O3) и, возможно, флаг "-finline-limit = n".

1 голос
/ 14 октября 2008

Одна хитрость, которую я прочитал где-то для того, чтобы оценить требования к стеку кода во встроенной установке, состоит в том, чтобы заполнить пространство стека в начале с известным шаблоном (DEAD в hex является моим любимым) и позволить системе работать в то время.

После обычного запуска прочитайте пространство стека и посмотрите, сколько пространства стека не было заменено в ходе работы. Спроектируйте так, чтобы оставить как минимум 150% от этого, чтобы охватить все пути кода obsure, которые, возможно, не были выполнены.

0 голосов
/ 14 сентября 2009

Я думаю, вы можете представить себе проблему, которой здесь нет. Большинство компиляторов фактически ничего не делают, когда они «распределяют» автоматические переменные в стеке.

Стек распределяется до выполнения функции main (). Когда вы вызываете функцию b () из функции a (), адрес области памяти сразу же после последней использованной переменной a передается в b (). Это становится началом стека b (), если b () затем вызывает функцию c (), а затем стек c начинается после последней автоматической переменной, определенной b ().

Обратите внимание, что стековая память уже есть и выделена, что инициализация не происходит, и единственная вовлеченная обработка - это передача указателя стека.

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

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

0 голосов
/ 14 сентября 2009

Да, ОСРВ действительно может использовать оперативную память для использования стека задач. По моему опыту, как новый пользователь ОСРВ, есть тенденция использовать больше задач, чем необходимо.

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

0 голосов
/ 18 мая 2009

Если вам нужно начать сохранять пространство стека, вы должны либо получить лучший компилятор, либо больше памяти.

Ваше программное обеспечение обычно расширяется (новые функции, ...), поэтому, если вам нужно начать проект, подумав о том, как сохранить место в стеке, он обречен с самого начала.

0 голосов
/ 27 сентября 2008

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

Как уже упоминалось в других ответах, встраивание - это одна из доступных опций, хотя это происходит за счет увеличения размера кода.

Другая область, которая ест стек - это локальные параметры. Эта область у вас есть некоторый контроль. Использование статики (на уровне файлов) позволит избежать выделения стеков за счет статического распределения памяти. Глобалы также.

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

0 голосов
/ 26 сентября 2008

Можете ли вы заменить некоторые локальные переменные глобальными? В частности, массивы могут съесть стек.

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

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

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

...