Можно ли написать приложение на C без использования кучи? - PullRequest
10 голосов
/ 22 июня 2009

Я испытываю столкновение стека / кучи во встроенной среде (см. этот вопрос для получения дополнительной информации)

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

Могу ли я написать приложение без использования кучи в C? Например, как бы я использовал стек, только если мне нужно динамическое выделение памяти?

Ответы [ 11 ]

28 голосов
/ 22 июня 2009

Я делал это однажды во встроенной среде, где мы писали «супер-безопасный» код для биомедицинских машин. Malloc () были явно запрещены, частично из-за ограничений ресурсов и неожиданного поведения, которое вы можете получить из динамической памяти (ищите malloc (), VxWorks / Tornado и фрагментацию, и у вас будет хороший пример).

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

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

8 голосов
/ 22 июня 2009

Как ни странно, я однажды увидел приложение базы данных, которое полностью полагалось на статическую распределенную память. Это приложение имело строгое ограничение по длине поля и записи. Даже встроенный текстовый редактор (я до сих пор так его называю) не смог создать текст с более чем 250 строками текста. Это решило некоторый вопрос, который у меня был в то время: почему на одного клиента разрешено только 40 записей?

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

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

Так что я могу ответить на ваш вопрос свежим и ясным "все зависит" ...

5 голосов
/ 22 июня 2009

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

См. man alloca.

Другой вариант - использовать массивы переменной длины, но вам нужно использовать режим C99.

3 голосов
/ 22 июня 2009

В main () можно выделить большой объем памяти из стека, и ваш код будет перераспределять его позже. Это глупо, поскольку это означает, что ваша программа занимает память, которая ей на самом деле не нужна.

Я не могу думать ни о какой причине (кроме какой-нибудь глупой задачи по программированию или учебного упражнения), желающей избежать кучи. Если вы «слышали», что выделение кучи медленное, а выделение стека быстрое, это просто потому, что куча включает динамическое распределение . Если бы вы динамически выделяли память из зарезервированного блока в стеке, это было бы так же медленно.

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

Редактировать: увидев мотивацию вопроса ...

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

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

2 голосов
/ 22 июня 2009

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

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

2 голосов
/ 22 июня 2009

Да, это выполнимо. Перенесите свои динамические потребности из памяти на диск (или в любую другую имеющуюся у вас память) и понесите в результате снижение производительности.

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

Этот метод динамически распределяет пространство, но это место на диске, а не в ОЗУ. Все используемые подпрограммы могут быть записаны с использованием статически выделенного пространства в стеке.

2 голосов
/ 22 июня 2009

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

2: Нет, я не думаю, что вы можете динамически распределять память в стеке, так как эта часть управляется компилятором.

2 голосов
/ 22 июня 2009

Вы не можете делать динамическое выделение памяти в C без использования кучи памяти. Было бы довольно сложно написать приложение реального мира без использования Heap. По крайней мере, я не могу придумать, как это сделать.

Кстати, почему вы хотите избежать кучи? Что с ним не так?

0 голосов
/ 23 июня 2009

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

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

0 голосов
/ 22 июня 2009

Я бы атаковал эту проблему по-другому - если вы думаете, что стек и куча сталкиваются, то проверьте это, защитившись от этого.

Например (при использовании системы * ix) попробуйте mprotect() последнюю страницу стека (при условии, что стопка фиксированного размера), чтобы она была недоступна. Или - если ваш стек увеличивается - тогда mmap страница в середине стека и куча. Если вы получили segv на своей странице защиты, вы знаете, что вы вышли из конца стека или кучи; и, посмотрев на адрес ошибки сегмента, вы увидите, какой из стека и кучи столкнулся.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...