Использует malloc () и free () действительно плохую идею на Arduino?

Использование malloc () и free () кажется довольно редким в мире Arduino. Он используется в чистом AVR C гораздо чаще, но все же с осторожностью.

Это действительно плохая идея использовать malloc () и free () с Arduino?

41 голос | спросил Cybergibbons 9 MarpmSun, 09 Mar 2014 13:00:25 +04002014-03-09T13:00:25+04:0001 2014, 13:00:25

7 ответов


36

Мое общее правило для встроенных систем - это только malloc () большие буферы и только один раз, в начале программы, например, в setup (). Проблема возникает, когда вы выделяете и деблокируете память. В течение долгого сеанса память становится фрагментированной, и в конечном итоге распределение выходит из строя из-за отсутствия достаточно большой свободной области, хотя полная свободная память более чем достаточна для запроса.

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

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

ответил JRobert 11 MarpmTue, 11 Mar 2014 20:36:25 +04002014-03-11T20:36:25+04:0008 2014, 20:36:25
15

Как правило, при написании эскизов Arduino вы избегаете динамического выделения (будь то с помощью malloc или new для экземпляров C ++), люди скорее используют глобальный -или static - переменные или локальные (стек) переменные.

Использование динамического распределения может привести к нескольким проблемам:

  • утечки памяти (если вы потеряли указатель на ранее выделенную память, или, более вероятно, если вы забудете освободить выделенную память, когда она вам больше не понадобится)
  • фрагментация кучи (после нескольких вызовов malloc /free), где куча растет больше, чем фактический объем выделенной памяти в настоящее время

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

MySketch.ino

#define BUFFER_SIZE 32
#include "Dummy.h"

Dummy.h

класс Dummy
{
    байтовый буфер [BUFFER_SIZE];
    ...
};

Без #define BUFFER_SIZE, если мы хотим, чтобы класс Dummy имел нефиксированный размер buffer, нам нужно было бы использовать динамическое распределение следующим образом:

класс Dummy
{
    const byte * buffer;

    общественности:
    Dummy (int size): buffer (новый байт [размер])
    {
    }

    ~ Пустышки ()
    {
        delete [] bufer;
    }
};

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

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

ответил jfpoilpret 9 MarpmSun, 09 Mar 2014 21:07:19 +04002014-03-09T21:07:19+04:0009 2014, 21:07:19
12

Я рассмотрел алгоритм, используемый malloc (), из avr-libc, и кажется быть несколькими шаблонами использования, которые безопасны с точки зрения кучи фрагментация:

1. Выделить только долгоживущие буферы

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

2. Выделить только недолговечные буферы

Значение: вы освобождаете буфер до выделения чего-либо еще. разумный пример может выглядеть так:

void foo ()
{
    size_t size = figure_out_needs ();
    char * buffer = malloc (размер);
    if (! buffer) fail ();
    do_whatever_with (буфер);
    свободный (буфер);
}

Если внутри do_whatever_with () нет malloc внутри , или если эта функция освобождает все, что он выделяет, тогда вы защищены от фрагментации.

3. Всегда освобождайте последний выделенный буфер

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

4. Всегда выделяйте один и тот же размер

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

ответил Edgar Bonet 1 MarpmSun, 01 Mar 2015 16:51:44 +03002015-03-01T16:51:44+03:0004 2015, 16:51:44
9

Использование динамического распределения (через malloc /free или new /delete)), по сути, не так, как например. На самом деле, для чего-то вроде строковой обработки (например, через объект String), это часто очень полезно. Это потому, что многие эскизы используют несколько небольших фрагментов строк, которые в итоге объединяются в более крупные. Использование динамического распределения позволяет использовать только столько памяти, сколько вам нужно для каждого. Напротив, использование статического буфера фиксированного размера для каждого из них может в конечном итоге тратить много места (что приводит к тому, что он будет работать значительно быстрее), хотя он полностью зависит от контекста.

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

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

ответил Peter Bloomfield 9 MarpmSun, 09 Mar 2014 22:32:03 +04002014-03-09T22:32:03+04:0010 2014, 22:32:03
6

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

Пример. У меня есть серийный пакетный класс (библиотека), который может принимать произвольные данные данных длины (может быть struct, array of uint16_t и т. д.). На отправляющем конце этого класса вы просто скажете методу Packet.send () адрес вещи, которую вы хотите отправить, и порт HardwareSerial, через который вы хотите его отправить. Однако на принимающей стороне мне нужен динамически выделенный буфер приема для хранения этой входящей полезной нагрузки, поскольку эта полезная нагрузка может быть другой структурой в любой момент, в зависимости от состояния приложения, например. ЕСЛИ я только отправляю одну структуру назад и вперед, я бы просто сделал буфер размером, который должен быть во время компиляции. Но в случае, когда пакеты могут быть разными по длине, malloc () и free () не так уж плохи.

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

//найдено на learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
int freeRam () {
    extern int __heap_start, * __ brkval;
    на телевидении;
    return (int) & v - (__brkval == 0? (int) & __ heap_start: (int) __brkval);
}

uint8_t * _tester;

тогда как (1) {
    uint8_t len ​​= random (1, 1000);
    Serial.println ( "-------------------------------------");
    Serial.println ("len is" + String (len, DEC));
    Serial.println ("RAM:" + String (freeRam (), DEC));
    Serial.println ("_ tester =" + String ((uint16_t) _tester, DEC));
    Serial.println ("alloating _tester memory");
    _tester = (uint8_t *) malloc (len);
    Serial.println ("RAM:" + String (freeRam (), DEC));
    Serial.println ("_ tester =" + String ((uint16_t) _tester, DEC));
    Serial.println («Заполнение _tester»);
    для (uint8_t i = 0; i <len; i ++) {
        _tester [i] = 255;
    }
    Serial.println ("RAM:" + String (freeRam (), DEC));
    Serial.println («освобождение памяти»);
    бесплатно (_tester); _tester = NULL;
    Serial.println ("RAM:" + String (freeRam (), DEC));
    Serial.println ("_ tester =" + String ((uint16_t) _tester, DEC));
    Задержка (1000); //беглый взгляд
}

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

ответил StuffAndyMakes 18 Maypm15 2015, 17:49:12
4
  

Это действительно плохая идея использовать malloc () и free () с Arduino?

Короткий ответ - да. Ниже приведены причины:

Это все о понимании того, что такое MPU и как программировать в рамках ограничений доступных ресурсов. Arduino Uno использует ATmega328p MPU с 32 КБ флэш-памятью ISP, 1024B EEPROM и 2 Кбайт SRAM. Это не так много ресурсов памяти.

Помните, что SRAM 2KB используется для всех глобальных переменных, строковых литералов, стека и возможного использования кучи. Стек также должен иметь головную комнату для ISR.

формат памяти :

 Карта SRAM

Сегодняшние ПК /ноутбуки имеют объем памяти более чем на 1.000.000 раз. Объем стека по умолчанию 1 Мбайт в потоке не является чем-то необычным, но совершенно нереалистичным для MPU.

Проект встроенного программного обеспечения должен выполнять бюджет ресурсов. Это оценка времени ожидания ISR, необходимого объема памяти, вычислительной мощности, циклов команд и т. Д. К сожалению, нет бесплатных обедов, а встроенное программирование в режиме реального времени является самым сложный навыков программирования для освоения.

ответил Mikael Patel 19 +03002017-10-19T13:55:16+03:00312017bEurope/MoscowThu, 19 Oct 2017 13:55:16 +0300 2017, 13:55:16
-3

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

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

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

ответил JSON 1 MaramSun, 01 Mar 2015 10:07:39 +03002015-03-01T10:07:39+03:0010 2015, 10:07:39

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132