C с минимальным ОЗУ

Я хочу понять управление памятью в программировании на C и C ++ для разработки приложений. Приложение будет работать на ПК.

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

Вот два момента в соответствии с тем, что я понимаю, но я не уверен:

(1) Используйте минимальные локальные переменные в main () и других функциях. Как локальные переменные сохраняются в стеке, то есть ОЗУ?

(2) Вместо локальных переменных используйте глобальные переменные сверху. Как глобальные переменные сохраняются в неинициализированной и инициализированной области ПЗУ?

Благодарю.

1) Обычно альтернатива распределению в стеке выделяется в куче (например, с помощью malloc ), которая на самом деле имеет большие накладные расходы из-за бухгалтерии / etc, а в стеке уже зарезервирована память для него, поэтому распределение по стеку, где это возможно часто предпочтительнее. С другой стороны, в стеке меньше места, а куча может быть близка к «неограниченному» в современных системах с виртуальной памятью и 64-битным адресным пространством.

2) На компьютерах и другой не встроенной системе все в вашей программе идет в ОЗУ, т. Е. Оно не мелькает в ROM-подобной памяти, поэтому глобальная и локальная не помогают в этом отношении. Кроме того, globals † имеют тенденцию «жить» до тех пор, пока приложение работает, тогда как местные жители могут выделяться и освобождаться (как в стеке, так и в куче) по мере необходимости, и, таким образом, предпочтительнее.

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

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

Если вам действительно нужно снизить объем памяти вашей программы, вы должны понимать, что все в программе хранится в ОЗУ, поэтому вам нужно работать над уменьшением количества и размера ваших вещей, а не пытаться жонглировать их местоположением. Другое место, где вы можете хранить вещи локально на ПК, это жесткий диск, поэтому храните там большие ресурсы и загружайте их по мере необходимости (желательно только точно необходимые детали). Но помните, что доступ к диску на несколько порядков медленнее, чем доступ к памяти, и что операционная система также может поменять местами на диск, если его память заполнена.

Сам программный код также сохраняется в ОЗУ, поэтому ваш компилятор оптимизируется для размера (опция -Os или /Os во многих распространенных компиляторах). Также помните, что если вы сохраните немного места в переменных, написав более сложный код, усилия могут быть отменены увеличенным размером кода; сохраните оптимизацию для больших выигрышей (например, сжатие больших ресурсов потребует добавленного кода декомпрессии, но все равно может дать большой чистый выигрыш). Использование динамически связанных библиотек (и других ресурсов) также помогает охвату всей памяти системы, если одна и та же библиотека используется одновременно несколькими программами.

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

Это сложно, потому что на вашем ПК у программы будет нехватка ОЗУ, если вы не сможете каким-то образом выполнить ее с ПЗУ или Flash.

Вот моменты, которые следует учитывать:

Уменьшите размер кода.
Код занимает ОЗУ.

Уменьшите количество и размер переменных.
Переменные должны где-то жить и что где-то в ОЗУ.

Сократите литералы символов.
Они тоже занимают место.

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

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

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

Редактирование 1 – Динамические библиотеки Слишком уменьшить объем памяти вашей программы, вы можете выделить область, в которой вы меняете библиотечные функции. Это похоже на концепцию DLL. Когда нужна функция, вы загружаете ее с жесткого диска в зарезервированную область.

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

Локальные переменные будут храниться с использованием некоторой комбинации регистров и пространства стека. Некоторые компиляторы будут использовать те же регистры или пространство стека для переменных, которые «живут» в разное время при выполнении программы; другие не будут. Кроме того, аргументы функции обычно вставляются в стек перед вызовом функции и удаляются при удобстве вызывающего абонента. При оценке кодовой последовательности:

 function1(1,2,3,4,5); function2(6,7,8,9,10); 

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

Если только «ПК», который вы разрабатываете, не соответствует сегодняшним стандартам, я бы не стал слишком беспокоиться о попытке микро-оптимизации использования ОЗУ. Я разработал код для микроcontrollerов с 25 байт ОЗУ и даже написанные полноценные игры для использования на микропроцессорной консоли с колоссальными 128 байт (не KBytes!) ОЗУ, и на такой силовой системе имеет смысл беспокоиться о каждом отдельном байте. Однако для ПК-приложений единственный раз, когда есть смысл беспокоиться о отдельных байтах, это когда они являются частью структуры данных, которая будет многократно реплицироваться в ОЗУ.

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

Когда вы используете «локальные» переменные, они сохраняются в стеке. Пока вы не используете слишком много стека, это в основном свободная память, как при возврате функции в память. Сколько «слишком много» варьируется … В последнее время мне пришлось работать в системе, где есть предел в 8 КБ данных для стека за процесс.

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

Я написал довольно сложный «диспетчер объектов» для проекта, который я написал несколько лет назад. Функция может использовать операцию «get» для заимствования объекта, а затем использовать операцию «release», когда она выполняется заимствованием объекта. Это означает, что все функции в системе могли совместно использовать относительно небольшой объем пространства данных, используя поочередно с помощью общих объектов. Вам решать, стоит ли вам строить «объект-менеджер», или если у вас достаточно памяти, чтобы просто использовать простые переменные.

Вы можете получить большую выгоду от «диспетчера объектов», просто называя malloc() и free() много. Тогда распределитель кучи управляет общим ресурсом, памятью кучи, для вас. Причина, по которой я написал собственный «диспетчер объектов», была необходимостью в скорости. Моя система использует идентичные объекты данных, и гораздо быстрее просто использовать повторное использование тех же, что и для их освобождения и повторного использования. Кроме того, моя система может быть запущена на чипе DSP, а malloc() может быть удивительно медленной функцией на некоторых архитектурах DSP.

Наличие нескольких функций, использующих одни и те же глобальные переменные, может привести к сложным ошибкам, если одна функция пытается удерживать глобальный буфер, а другая – переписывает данные. Таким образом, ваша программа, вероятно, будет более надежной, если вы будете использовать malloc() и free() если каждая функция записывает только в данные, которые она сама выделяет. (Но malloc() и free() могут вводить собственные ошибки: утечки памяти, двойные ошибки, продолжая использовать указатель после того, как данные, на которые он указывает, были освобождены … если вы используете malloc() и free() обязательно используйте такой инструмент, как Valgrind, чтобы проверить свой код.)

Любые переменные по определению должны храниться в памяти чтения / записи (или ОЗУ). Если вы говорите о встроенной системе с исходным кодом в ROM, тогда среда выполнения скопирует выбранный вами образ ROM в ОЗУ для хранения значений глобальных переменных.

Только элементы, обозначенные как неизменяемые (const), могут храниться в ПЗУ во время выполнения.

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

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

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

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