Dynamic memory allocation in Arduino might sound like a complex concept, but it’s essentially about managing the memory of your Arduino board efficiently.
At the heart of this are two functions: malloc()
and free()
.
Let’s unravel the functionality of these functions and see how they can be useful (and sometimes problematic).
Understanding malloc() and free()
malloc()
, short for “memory allocation”, is used when you need to allocate a specific amount of memory during the runtime of your program. It gives you control over how much memory you use, depending on what your program needs at any given time. Once you’re done with the allocated memory, you use free()
to release it back to the system.
Why does this matter?
Arduino boards have limited memory. Using malloc()
and free()
lets you optimize memory usage, especially when you’re unsure how much memory you will need when writing your program.
When Dynamic Memory Allocation is Useful
Imagine you’re creating a data logger that records temperature readings. The number of readings might vary significantly.
Using malloc()
, you can allocate just enough memory to store these readings rather than reserving a large array from the start.
Another scenario could be if you’re working with text data that changes in size, like strings from a sensor or a network. Dynamic memory allocation allows you to adjust the memory used based on the size of the incoming data.
Misuse of Dynamic Memory Allocation
However, dynamic memory allocation is an advanced technique that can cause problems if not used properly. Misusing it can lead to problems like memory leaks and fragmentation. A memory leak happens when you allocate memory using malloc()
but forget to free it with free()
. Over time, this eats up the memory, leading to crashes or unexpected behaviour.
Fragmentation is another risk. It occurs when you frequently allocate and deallocate memory in small chunks, leaving the memory space fragmented and inefficient. Think of it like trying to fit different-sized books neatly on a shelf. Over time, finding space for new books becomes hard even though there’s technically enough space overall.
A simple example of Dynamic Memory Allocation
Let’s look at an example where we use malloc()
and free()
to dynamically manage memory in an Arduino Uno R3 project.
In this example, we’ll simulate a situation where you’re reading a variable number of sensor readings and storing them in an array. The array size will depend on how many readings we need, which we’ll determine at runtime.
#include <Arduino.h>
int *readings; // Pointer for our dynamic array
int numReadings; // Number of readings to store
void setup() {
Serial.begin(9600);
// Let's assume we determine the number of readings dynamically
// For demonstration, we're setting it manually here
numReadings = 10;
// Allocate memory for the readings
readings = (int *)malloc(numReadings * sizeof(int));
// Check if memory allocation was successful
if (readings == NULL) {
Serial.println("Memory allocation failed");
return;
}
// Simulate reading sensor data and store it in the array
for (int i = 0; i < numReadings; i++) {
readings[i] = analogRead(A0); // Replace with actual sensor reading logic
delay(100); // Delay for demonstration purposes
}
// Print the readings
for (int i = 0; i < numReadings; i++) {
Serial.print("Reading ");
Serial.print(i);
Serial.print(": ");
Serial.println(readings[i]);
}
// Free the allocated memory when done
free(readings);
}
void loop() {
// Your loop code here
}
Here are a few pointers to help you better understand this example code:
- We’re dynamically allocating an array to store sensor readings using
malloc()
. - The size of the array is determined at runtime, showcasing the flexibility of dynamic memory allocation.
- After using the array, we release the allocated memory using
free()
. - We include a check to ensure that the memory allocation was successful. This is crucial in a good practice scenario to avoid undefined behaviour if
malloc()
fails.
I have also prepared an example code where dynamic memory allocation is used incorrectly. Check this out.
Dynamic Memory allocation done badly
Let’s look at an example where malloc()
and free()
are used incorrectly in an Arduino sketch. In this scenario, we’ll simulate a common mistake where memory is allocated in a loop without being freed properly, leading to a memory leak.
#include <Arduino.h>
void setup() {
Serial.begin(9600);
}
void loop() {
int *data;
// Allocate memory for an array of 10 integers
data = (int *)malloc(10 * sizeof(int));
// Check if memory allocation was successful
if (data == NULL) {
Serial.println("Memory allocation failed");
return;
}
// Simulate some operations with the data
for (int i = 0; i < 10; i++) {
data[i] = i;
}
// Intentionally forgetting to free the allocated memory
// free(data);
// Delay for demonstration purposes
delay(1000);
// The loop continues, allocating more memory each time without freeing the previous allocation
}
Let’s look at what is going on in this example:
- Memory is allocated each time the
loop()
function runs but is never freed. This results in a memory leak, where the Arduino’s limited memory gets used up over time without release. - The repeated allocation without deallocation will eventually exhaust the available memory, leading to system instability or crashes.
- This is a classic example of improper use of dynamic memory allocation, showcasing how forgetting to use
free()
can cause serious issues in embedded systems like Arduino.
This code is a cautionary example of how mismanagement of dynamic memory can lead to memory leaks, particularly problematic in resource-constrained environments like the Arduino.
The “one thing” to take away
Dynamic memory allocation can be a powerful tool in your Arduino programming arsenal. It gives you flexibility and efficiency in managing memory. However, it requires a thoughtful approach.
Always remember to free up memory when it’s no longer needed, and be cautious about allocating and deallocating memory frequently.