The use of malloc()
and free()
seems pretty rare in the Arduino world. It is used in pure AVR C much more often, but still with caution.
Is it a really bad idea to use malloc()
and free()
with Arduino?
The use of malloc()
and free()
seems pretty rare in the Arduino world. It is used in pure AVR C much more often, but still with caution.
Is it a really bad idea to use malloc()
and free()
with Arduino?
My general rule for embedded systems is to only malloc()
large buffers and only once, at the start of the program, e.g., in setup()
. The trouble comes when you allocate and de-allocate memory. Over a long run session, memory becomes fragmented and eventually an allocation fails due to lack of a sufficiently large free area, even though the total free memory is more than adequate for the request.
(Historical perspective, skip if not interested): Depending on the loader implementation, the only advantage of run-time allocation vs. compile-time allocation (intialized globals) is the size of the hex file. When embedded systems were built with off the shelf computers having all volatile memory, the program was often uploaded to the embedded system from a network or an instrumentation computer and the upload time was sometimes an issue. Leaving out buffers full of zeros from the image could shorten the time considerably.)
If I need dynamic memory allocation in an embedded system, I generally malloc()
, or preferably, statically allocate, a large pool and divide it into fixed-size buffers (or one pool each of small and large buffers, respectively) and do my own allocation/de-allocation from that pool. Then every request for any amount of memory up to the fixed buffer size is honored with one of those buffers. The calling function doesn't need to know whether it's larger than requested, and by avoiding splitting and re-combining blocks we solve fragmentation. Of course memory leaks can still occur if the program has allocate/de-allocate bugs.
Update 02/16/23:
I'm curious why the Arduino library's malloc implementation doesn't implement some coalescing of the free blocks like a full OS would.
That's interesting to think about, but let's first be clear that in C/C++, malloc() and free() are implemented as library functions at the application-level not the OS level, even in major OSes. And they are based on fragmenting the heap, just as the Arduinos' malloc() functions do.
Embedded systems, are relatively new, at least as an influential force in the design of OSes, languages, and libraries' design. Which is to say, programs that had to run "forever" without failing (other than, perhaps the OS itself) were outliers. Most programs we run on our desktops or (once upon a time) ran on mainframes, were started, processed a batch of data, and exited.
Secondly, the malloc()/free() method of memory allocation/deallocation was simple, where anticipating the sizes of memory requests was not; and some allocations are used briefly and returned, where some are kept for the duration of the run. How is a library designer to provide for coalescing currently free and newly deallocated memory with no knowledge of the allocate/deallocate usage and sizes of requests? The fixed-size buffer scheme I described above doesn't suffer the fragmentation problem that "malloc() an arbitrary number of bytes" scheme does. It is still limited to available memory but within that limitation, could meet the "run forever" requirement. There may be algorithms that split and coalesce memory, but probably only for a few - statistically predictable - patterns of allocation and deallocation.
Update 02/18/23:
@edgarbonet points out that Arduino's free()
does try to coalesce free blocks. This is a good example of an algorithm that works for certain cases: Properly done, it would work for "last-out / first-in" cases, and to some extent for some other cases.
The trouble is, most of our use patterns are not very regular, if they're regular at all. An alogrithm that does no splitting, but merely allocates & deallocates fixed-size pieces (rather like loaning library books) can still run out of memory but won't end up with fragmented resources. In fact, because it doesn't split, it will need more memory to meet the same random-sized requests than even a coalescing malloc()
and free()
.
malloc()
/free()
does coalesce adjacent free blocks.
Commented
Feb 17, 2023 at 20:28
I have taken a look at the algorithm used by malloc()
, from avr-libc,
and there seems
to be a few usage patterns that are safe from the point of view of heap
fragmentation:
By this I mean: allocate all you need at the beginning of the program, and never free it. Of course, in this case, you could as well use static buffers...
Meaning: you free the buffer before allocating anything else. A reasonable example might look like this:
void foo()
{
size_t size = figure_out_needs();
char * buffer = malloc(size);
if (!buffer) fail();
do_whatever_with(buffer);
free(buffer);
}
If there is no malloc inside do_whatever_with()
, or if that function
frees whatever it allocates, then you are safe from fragmentation.
This is a generalization of the two previous cases. If you use the heap
like a stack (last in is first out), then it will behave like a stack
and not fragment. It should be noted that in this case it is safe to
resize the last allocated buffer with realloc()
.
This will not prevent fragmentation, but it is safe in the sense that the heap will not grow larger than the maximum used size. If all your buffers have the same size, you can be sure that, whenever you free one of them, the slot will be available for subsequent allocations.
Typically, when writing Arduino sketches, you will avoid dynamic allocation (be it with malloc
or new
for C++ instances), people rather use global -or static
- variables, or local (stack) variables.
Using dynamic allocation can lead to several problems:
malloc
/free
calls) where the heap grows bigger thant the actual amount of memory allocated currentlyIn most situations I have faced, dynamic allocation was either not necessary, or could be avoided with macros as in the following code sample:
MySketch.ino
#define BUFFER_SIZE 32
#include "Dummy.h"
Dummy.h
class Dummy
{
byte buffer[BUFFER_SIZE];
...
};
Without #define BUFFER_SIZE
, if we wanted Dummy
class to have a non-fixed buffer
size, we would have to use dynamic allocation as follows:
class Dummy
{
const byte* buffer;
public:
Dummy(int size):buffer(new byte[size])
{
}
~Dummy()
{
delete [] bufer;
}
};
In this case, we have more options than in the first sample (e.g. use different Dummy
objects with different buffer
size for each), but we may have heap fragmentation issues.
Note the use of a destructor to ensure dynamically allocated memory for buffer
will be freed when a Dummy
instance is deleted.
I disagree with people who think you shouldn't use it or it is generally unnecessary. I believe it can be dangerous if you don't know the ins and outs of it, but it is useful. I do have cases where I don't know (and shouldn't care to know) the size of a structure or a buffer (at compile time or run time), especially when it comes to libraries I send out into the world. I agree that if you're application is only dealing with a single, known structure, you should just bake in that size at compile time.
Example: I have a serial packet class (a library) that can take arbitrary length data payloads (can be struct, array of uint16_t, etc.). On the sending end of that class you simply tell the Packet.send() method the address of the thing you wish to send and the HardwareSerial port through which you wish to send it. However, on the receiving end I need a dynamically allocated receive buffer to hold that incoming payload, as that payload could be a different structure at any given moment, depending on the application's state, for instance. IF I'm only ever sending a single structure back and forth, I'd just make the buffer the size it needs to be at compile time. But, in the case where packets may be different lengths over time, malloc() and free() are not so bad.
I've run tests with the following code for days, letting it loop continuously, and I've found no evidence of memory fragmentation. After freeing the dynamically allocated memory, the free amount returns to its previous value.
// found at learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
uint8_t *_tester;
while(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("Filling _tester");
for (uint8_t i = 0; i < len; i++) {
_tester[i] = 255;
}
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("freeing _tester memory");
free(_tester); _tester = NULL;
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
delay(1000); // quick look
}
I haven't seen any sort of degradation in RAM or in my ability to allocate it dynamically using this method, so I'd say it's a viable tool. FWIW.
Using dynamic allocation (via malloc
/free
or new
/delete
) isn't inherently bad as such. In fact, for something like string processing (e.g. via the String
object), it's often quite helpful. That's because many sketches use several small fragments of strings, which eventually get combined into a larger one. Using dynamic allocation lets you use only as much memory as you need for each one. In contrast, using a fixed-size static buffer for each one could end up wasting a lot of space (causing it to run out of memory much faster), although it depends entirely on the context.
With all of that being said, it's very important to make sure memory usage is predictable. Allowing the sketch to use arbitrary amounts of memory depending on run-time circumstances (e.g. input) can easily cause a problem sooner or later. In some cases, it might be perfectly safe, e.g. if you know the usage will never add up to much. Sketches can change during the programming process though. An assumption made early-on could be forgotten when something is changed later, resulting in an unforeseen problem.
For robustness, it's usually better to work with fixed-size buffers where possible, and design the sketch to work explicitly with those limits from the outset. That means any future changes to the sketch, or any unexpected run-time circumstances, should hopefully not cause any memory problems.
Is it a really bad idea to use malloc() and free() with Arduino?
The short answer is yes. Below are the reasons why:
It is all about understanding what an MPU is and how to program within the constraints of the available resources. The Arduino Uno uses an ATmega328p MPU with 32KB ISP flash memory, 1024B EEPROM, and 2KB SRAM. That is not a lot of memory resources.
Remember that the 2KB SRAM is used for all global variables, string literals, stack and possible usage of the heap. The stack also needs to have head room for an ISR.
The memory layout is:
Todays PC/laptops have more than 1.000.000 times the amount of memory. A 1 Mbyte default stack space per thread is not uncommon but totally unrealistic on an MPU.
An embedded software project has to do a resource budget. This is estimating ISR latency, necessary memory space, compute power, instruction cycles, etc. There are unfortunately no free-lunches and hard real-time embedded programming is the most difficult of programming skills to master.
Ok, I know this is an old question but the more I read through the answers the more I keep coming back to an observation that seems salient.
There seems to be a link with Turing's Halting Problem here. Allowing dynamic allocation increases the odds of said 'halting' so the question becomes one of risk-tolerance. While it is convenient to wave off the possibility of malloc()
failing and so forth, it is still a valid outcome. The question the OP asks only appears to be about technique, and yes the details of the libraries used or the specific MPU do matter; the conversation turns towards reducing the risk of the program halting or any other abnormal end. We need to recognize the existence of environments that tolerate risk vastly differently. My hobby project to display pretty colors on a LED-strip won't kill someone if something unusual happens but the MCU inside a heart-lung machine likely will.
For my LED-strip, I don't care if it locks up, I'll just reset it. If I were on a heart-lung machine controlled by an MCU the consequences of it locking up or failing to operate are literally life and death, so the question about malloc()
and free()
should be split between how the intended program deals with the possibility of demonstrating Mr. Turing's famous problem. It can be easy to forget that it is a mathematical proof and to convince ourselves that if only we are clever enough we can avoid being a casualty of the limits of computation.
This question should have two accepted answers, one for those who are forced to blink when staring The Halting Problem in the face, and one for all others. While most uses of the arduino are likely not mission critical or life-and-death applications, the distinction is still there regardless of which MPU you may be coding.