Подтвердить что ты не робот

Как сохранить пространство стека с хорошим дизайном?

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

В вашем использовании стека есть 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 поддерживает локальное хранилище, которое выделяет "глобальное" хранилище для каждого потока. Это может позволить вам иметь несколько независимых глобальных переменных для каждой задачи, но это сделает ваш код не таким простым.

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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

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

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

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