How to create and free dynamic arrays with Arduino

WARNING: This is an attempt to clarify if there is any safe way to use dynamic arrays on Arduino Platform ! The recommended way often is to avoid dynamic memory allocation due to the missing memory manager who would clean up SRAM after the memory space is freed again. If you want to use dynamic arrays please read the discussion carefully and do NOT start before reading the end.

However - despite of that - from time to time there seems to be some interest in dynamic arrays in Arduino applications. Therefore I looked up some old C code implementations from 1989 and compiled them into a sketch that I tested with IDE1.8.15 and an UNO.

The sketch may be of assistance (at least for 2D arrays) and to demo the basic principle which can be adopted to multidimensional arrays. Experts in the field are welcome to correct and enhance the sketch which I publish here as a start for those who want to use dynamic arrays in their applications.

There are only two functions in the example sketch which are required in an application to create/free 2D arrays that can be addressed like MyArray[irow][col]::

  • int **idim2(int row, int col)

and

  • void ifree2(int **pa)

Take care to declare the array variable as int **MyArray.

If you need byte or long instead of arrays of integer,you may adopt the idim2 routine.

Be aware that dynamically allocating memory may lead to failures if your application falls short of free heap space ...

// "Old School" creation of dynamic arrays
// based on an example from a C language course   
// held in ... 1989 ...
// written by ec2021
//
//
// The following routine is only used to check memory allocation in this example!
// .........................
// Begin of Memory Routine
// .........................
extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;

int freeMemory() {
  int free_memory;
  if((int)__brkval == 0)
     free_memory = ((int)&free_memory) - ((int)&__bss_end);
  else
    free_memory = ((int)&free_memory) - ((int)__brkval);
  return free_memory;
}
// .........................
// End of Memory Routine
// .........................

// .........................
// Begin of idim2()
// This is where the "real work" is done
// to create the 2D array
// First the memory for all (row*col) data is allocated
// if this does not fail, memory for the pointers to the beginning
// of each row is allocated
// If this is also done successfully (calloc returns NULL in case of 
// failure), the pointers are initialized with the address of their
// specific row
// The returned value points to the beginning of the pointer array
// thus allowing the compiler to address rows and cols via "a[rows][cols]"
// .........................
int **idim2(int row, int col)
{
 // char *calloc();
  int i;
  register int **prow, *pdata;
  pdata = (int *) calloc(row * col, sizeof(int));
  if (pdata == (int *) NULL ){
   Serial.println("No heap space for data"); 
   exit(1);
  };
  prow = (int **)calloc(row, sizeof (int *));
  if (prow == (int **) NULL) {
   Serial.println("No heap space for row pointers");
   exit(1); 
  };
  for (i = 0; i < row; i++) {
    prow[i] = pdata;
    pdata += col;
  };
  return prow;
}
// .........................
// End of idim2()
// .........................

 void ifree2(int **pa)
 {
  free(*pa);
  free(pa);
 }


void Create2D(int rowsc, int colsc) {
  int **a;
  int inum = 1;
  register int i, j;
  char out[256];

  int memBefore = freeMemory();
  Serial.println("\n..........................................................");
  Serial.println("Free memory before calloc: "+String(memBefore));
 
  a = idim2(rowsc, colsc);
 
  Serial.println("Free memory after calloc: "+String(freeMemory()));
  Serial.println("Memory used             : "+String(memBefore-freeMemory()));
 
  for (i = 0; i < rowsc; i++)
    for (j = 0; j < colsc; j++)
       a[i][j] = inum++;
  char Tab = 9;  
  for (i = 0; i < rowsc; i++)
    for (j = 0; j < colsc; j++) {
       sprintf(out,"%3d%2c",a[i][j],(j+1) % colsc ? ' ' : '\n');
       Serial.print(out);
      }

  ifree2(a);
}

void setup() {
  // Runs once
  Serial.begin(115200);
  Create2D(2,3);
  Create2D(8,8);
  Create2D(7,12);
  Create2D(4,16);  
}
 
void loop() {
  // Nothing to do ...
}

Arduino is C++ so you can use int arr[] = new int[arrsize]; and delete arr;
but don't use free and delete on Arduino. it fragments the heap and there is no memory manager.
use global variables and variables on stack or use only allocation on heap (if the size is determined at runtime)

Appreciated, but does this work with more than 1D arrays that allows to address the usual way rather than writing a function that converts e.g. [row][col] to [row*NoOfCols + col] (which I have actually done in another case)?

The missing memory manager may cause problems like you mentioned. But would you think that the method would be not problematic if only used once during runtime of a sketch e.g. to adopt to a specific configuraton (same code but different array dimensions depending on the external application)?

There is of course the DynamicArrayHelper but that is also only making use of 1D arrays.

Just would be nice to have a useable method to handle the capability of 2D arrays ...

The thing is, if you only use it once, you still have to make sure there is sufficient memory available to handle the largest possible allocation, so why not declare the array at compile time with the maximum size and only use as much as needed at runtime? If you have a number of possible configurations of that array, within the same memory space, then declare it as a union with each potential configuration and choose which to use at runtime.

I think the declaration as a union with different potential configuration might be a valid solution if one allocates the space required for the largest array, e.g.

  • a[4][16]
  • a[8][8] and
  • a[7][12]

would require 64 x sizeof(type) in both first and 84 x sizeof(type) bytes in the last array, so one had to go for the maximum. That should normally cause no problem. Would you mind to supply a code example?

Thanks @Juraj , that page did not appear on my "radar screen" up to now, excellent!

What I understand - for the calloc() routine - is that if memory space is allocated after the dynamic allocation of an array, let's say for the local variable "int dummy", even if you delete or free the dynamic array again, the memory space of that array is now a "hole" between the space where "dummy" is stored and the last variable space before the dynamic allocation. There is no Janitory who cleans up the mess and you may run out of memory sooner or later with unpredictable effects on your code.

However, what (except of course using C++ methods) is the difference to the calloc() routine from the very past? The procedure seems to use the same sequence , first allocate data space, then row pointers and then remove them in reverse order. I am not argueing, just curious to understand the difference!

P.S.: Anyway I will shortly put a "warning" in the first post of my thread :wink: [Done]...

@Juraj and @david_2018 : Thanks to both of you for your kind and helpful support!

I found the following link that neatly describes the memory usage in Arduino.

If you still feel the need to use dynamic memory allocation, you may read this
Having Fun with Arduino's Memry

The rejection of using malloc() or calloc() with C++ is based on the difference that the operator new can handle objects and is more type-safe. As far as I understand it will not solve the missing memory manager issue.

So if you are sure just to handle plain old date types and you use it carefully, your code might have no problems. But that is at your own risk... :wink:

It’s not that the avr is missing a “memory manager”, it’s just that no memory manager can do a very good job with as little ram as exists on those platforms.

I wondered about this. I can't imagine anyway that it would be simple to have a memory manager in C++ which can deal with heap fragmentation. Or, at least, not in the sense of a Windows file defragmenter or a general garbage collector or similar.
One problem would be the resolution all references to the moved items which could be deeply hidden. I suppose the other issue would be the unpredictable run time while everything is blocked by the "memory manager"

@6v6gt and @westfw : Maybe it is like with racing cars versus trucks :wink: Quick and lightweight but little load to carry or the other way around ...

So best practice seems to be to avoid dynamic memory allocation due to the hazard of memory fragmentation and crash between heap and stack.

If one still is keen to define arrays during runtime it might be a good idea to do this in a function with a local array but also check for available memory with some preserved space to avoid weird program behaviour. You may check the following sketch that I have tested empirically without encountering problems (maybe bad luck ...?!?) although I stressed it by recursive calls. It works of course ONLY with pod (plain old data like byte, int etc.). By using a while-loop instead of the standard main loop() you keep almost all variables in the local function thus utilizing the standard way of allocating and freeing data without the need to take care of that on your own:

// A const value that preserves bytes 
// to (hopefully) avoid running out of memory between heap and stack
// that might cause weird behaviour of the program
const byte c_reserve = 200;

// The depth of the recursive use of the function UseArray()
// Recursive calls are only used in this example to definetely 
// stress memory allocation and see what happens ...

int depth;

// The main function to create arrays in runtime
// Most lines are only useful for this example
// The core lines required are
// const byte c_reserved = 200;
//  void UseArray(byte Rows, byte Cols, byte reserve){
//      if (Cols*Rows*sizeof(WhatEverTypeOfPOD) + reserve >= freeMemory()){ return };
//      WhatEverTypeOfPOD MyA[Rows][Cols];
//      while(YouWantToLoop) {
//        // do Something in here
//        }
//     }
// With 
//  "WhatEverTypeOfPOD" may be any type of **plain old data**, such as byte, int, long, float
//  do not use this with types/objects that require constructor/destructor calls!
//  Cols, Rows = 1 or more
//  c_reserved = 200 or more (no gurantee, but worked fine in this stress test ...)

// forward declaration of getFreeMemory()
int getFreeMemory();

void UseArray(byte Rows, byte Cols, byte reserve){
  depth++;
  Serial.println("Depth = "+String(depth));
  if (Cols*Rows*sizeof(int) + reserve >= getFreeMemory()){
      Serial.print  ("Use Array ["+String(Rows)+"]["+String(Cols)+"] requires "+String(Cols*Rows*sizeof(int))+ " Bytes ");
      Serial.println("plus "+String(reserve)+" reserved bytes, but there are only "+String(getFreeMemory())+" Bytes available");
      return;
    }
  int MyA[Rows][Cols];
  static int Test = 1;
  Serial.println("Test = "+String(Test++));
  Serial.println("Use Array ["+String(Rows)+"]["+String(Cols)+"] with Free Memory = "+String(getFreeMemory()));
  
  for (int i= 0;i<Rows;i++) 
    for (int j= 0;j<Cols;j++) MyA[i][j] = (i+1)*100+j;
 
  for (int i= 0;i<Rows;i++){ 
    for (int j= 0;j<Cols;j++)
      Serial.print(String(MyA[i][j])+char(9));
    Serial.println();  
    }
 
   // In this test example you start recursive 
   // "dynamic" array creation with an 's' per Serial in the
   // following while-loop.
   // It will stop here again after all recursive function calls
   // have come back to the first call in the main loop()
   // where depth is reset to zero (see below)
   //
   boolean YouAreHappy = (depth == 1); 
   if (YouAreHappy) Serial.println("\nStart recursive calls per Serial input with 's' "); 
   while (YouAreHappy) {
    // Put your loop() content here
    if (Serial.available()){
      YouAreHappy = (Serial.read() != 's');
      }
    // End of loop() example  
    }
    UseArray(random(2,21),random(2,21),c_reserve);
    depth--;
    Serial.println("Returned to "+String(depth));
  }

void setup() {
  Serial.begin(115200);
  Serial.println("\n-----------------------------");
}

void loop() {
  Serial.println("Free Memory in loop() = "+String(getFreeMemory()));
  depth = 0;  
  UseArray(random(2,21),random(2,21),c_reserve);    
}

extern unsigned int __bss_end;
extern void *__brkval;

int getFreeMemory(){
    int free_memory;
    if((int)__brkval == 0) free_memory = ((int)&free_memory) - ((int)&__bss_end);
                      else free_memory = ((int)&free_memory) - ((int)__brkval);
    return free_memory;
}


Wow! If I believed everything in this thread, I would never, never, EVER use dynamic allocation on any Arduino. Ever! Fortunately, I know better. To say "there is not memory manager" is nonsense. Of course there is a memory manager. What is missing is automatic GARBAGE COLLECTION, which is a "feature" of c and c++, regardless of platform.

Does that mean using dynamic allocation is guaranteed to fragment memory and make your applcation unstable? No, it does not. The memory manager (Yes, there really is one!) manages heap allocation, using a linked list of allocated blocks. When a new allocation is performed, a link is added to the list, ot track the newly allocated block. When that block is freed, the newly freed block is combined with adjacent free blocks, creating the largest possible free blocks between/around any remaining allocated blocks. So, memory does not automatically become fragmented any more than absolutely necessary. If you allocate a bunch of blocks, and then de-allocate all of them, the free memory pool will look exactly as it did when you started. I have written programs that used malloc and free to allocate, print, and discard large numbers of debug messages. Those programs run for days with no errors. You just have to have reasonable expectations for how much memory you can use at any given time, and not assume you can run with 95% of RAM allocated at all times. Using Strings certainly makes things worse, as it can significantly increase the number of allocations, but, even then, if your expectations are reasonable, they will work just fine.

Finally, 99.99% of dynamic allocation problems are self-inflicted. If you are going to use it, you MUST pay attention to return codes, and figure out how to gracefully handle failed allocations. Most people just do a malloc or new, and assume it succeeds. THAT is a recipe of disaster. Once an allocation fails, if it is not dealt with, things will go downhill in a big hurry.

And if you want to see how it all works, there are numerous functions out there to display the heap and stack status, and you can even walk the heap linked list yourseld and see EXACTLY what malloc, free, new, etc. do, and HOW they do it. Or, simply look at the source code. It's really pretty simple. The detailed implementations ARE different on different platforms, so it's not exactly the same on AVR-based boards as it is on ARM-based boards. But the basic principles are the same on all platforms.

I am glad you showed up here, because my feeling is that the issue with dynamic memory management on Arduino platform seems to split the community and many of the statements are based on partial knowledge or at least partial information given ...

That's why I started this tread and came up with the "old fashioned" C calloc, others brought in the C++ way of new / delete and I finally wrote a sketch that works fine without all of the above (because it relies on the standard way of arranging heap/stack memory with local functions).

I recon that it might help to get some more light into the dark when there is a definition of what we consider to be a "memory manager":

  • The minimum memory management might be to allocate memory and then free it in reverse sequence, whether it is heap or stack. If done in the right order it should normally not generate any fragmentation. (Or not?)
  • If freeing of memory is not done in exactly the reverse order you might get "holes" in the SRAM that could be reused by a simple memory management for the same data type (or something using less bytes leaving smaller "holes").
  • Finally, memory mangement of larger systems, especially an OS, would do some garbage collection invisible to the functions/procedures by copying memory content and rearranging pointers to those data. That seems to be a more demanding task as the memory manager has to know exactly who is using what part of the memory and must make sure that noone accesses data during the re-arrangement . Correct (in principle)?

It would be nice if some experts could create a documentation of if ever and how you can safely use dynamic memory allocation with Arduino. I am surely not one of those experts, but highly interested in a sound description that allows to understand the background rather than just Do's and Donot's ... :wink:

Any idea how to bring light into the dark and disenchant the mystery around dynamic memory allocation on Arduino platforms ... without leaving all the google work to thousands of curious people?

The order of allocation/de-allocation is NEVER up to the memory manager. It is up to the user/application. The order of allocation/de-allocation is not, should not, be important. Once all allocated block have been freed, you will have a single block of free memory, exactly as when the system was first initialized. That is precisely how it works in Arduino world right now.

Yes, and that will always be true, UNLESS you add the considerable complexity of automatic background garbage collection, which c/c++ do not support. That would mean be able to move things around in memory at any time, without disrupting the running applications. In a language that uses pointers/references, that means the values of those pointers/references must be update on-the-fly, WITHOUT the application even knowing it is happening. Even with a full virtual memory system (which few, if any, Arduinos have, the overhead and complexity to do that is quite considerable. This is one of the reasons languages which DO support garbage collection, like Javascript, are so slow and resource-intensive.

Yes, but again, that approach is quite complex and resource intensive, and NOT supported by c/c++, regardless of the OS. And not remotely practical on a small microcontroller. It will require more memory, and the entire system will run considerably slower.

You can look at the avr implementation of malloc() and free() - it's all open source code, after all. "new" and "delete" on Arduino are just wrappers for malloc/free.
avr-libc malloc and free

It's not trivial code; probably on par with the original K&R implementations. It will even aggregate free()ed memory blocks into bigger blocks. It just doesn't have a lot to work with (for example, a "better" malloc() might keep separate free lists for different "popular" sizes of allocation. I think I even have in mind a "rule" that says malloc implementations will always grow in complexity until you also need to implement special purpose memory allocation functions that are simpler and faster.) But now you're talking about more overhead for each allocated or free block, and when you've only got 2k of memory, 10+ bytes of overhead per allocation is painful.

C/C++ does not do "garbage collection" in the way the term is usually used - freeing up memory that is no longer in use, but was never explicitly free()ed, either.

The AVR does not have other users who will use the memory that you've freed. It does not have "protection" against over-allocating memory from the heap, and I don't think it even it has checks for the stack overflowing INTO the heap. It does not have "segmentation error" capability if you accidentally access memory that doesn't exist. There is probably no want to indicate to a user that a memory allocation failure has occured, or that the board is "running low."

I feel vaguely comfortable using malloc() during setup() to make memory for alternative configurations. For example, I think the Adafruit neopixel library will malloc() the data for the actual pixels, and I'm basically OK with that.

The Arduino String functions are a particularly poor example of using dynamic allocation, allocating tiny little chunks in random order in ways that make it difficult to re-aggregate the memory.

I guess my general rule of thumb is something like:

  • You can use dynamic allocation in cases where it is immediately apparent if the allocation fails.
1 Like

The other thing to bear in mind with malloc() is that it is not re-entrant (at least on AVR) so no one should write an task [ISR] which can asynchronously grab memory while it is being released in the main loop(), which would otherwise mess up any attempts by the programmer to minimise fragmentation of the heap by structuring the use of malloc() and free() to cleanly unwind any storage allocations. I discovered this or, better put, it was pointed out to me by @drmpf during an sustained attempt to "break" the String class which, incidentally, I found surprising difficult when following all the rules. Convert string array to byte array - #8 by 6v6gt

malloc() must return return a null pointer if it can't allocate the requested memory. So, in that sense, there is protection against over-allocating. The application code should check the returned pointer for this case and behave accordingly.

@RayLivingston , @6v6gt , @Juraj , @gfvalvo , @david_2018 and @westfw:

Thanks for your comments and examplary way to discuss this matter!

You all brought at least my understanding forward.

My conclusions so far are:

  • If required feel free to use dynamic memory allocation for plain old data, but follow some rules:
    • Check memory and handle the situation that there is not enough space available
    • Take care to free memory again after usage (where using array decaration in local functions is the easiest way, see below,, but of course works with malloc/calloc/new as well)

Leaving some hundred bytes free between heap and stack seems to be a good rule of thumb.

Today I found the following link regarding Arduino memory structure:
managing arduino memory

Just scroll down to "Optimizing SRAM" (at about 50%) to find further information about heap fragmentation and the use of stack memory incl. the benefit of using local variables (which stay in the stack and are easily freed after leaving the function).

Unfortunately one can never be sure that something found in the internet is true, but it feels to be quite sound ...

On AVR board there is some protection against stack overflowing INTO the heap. The memory allocate always ensures there is at least 128bytes left available for stack use. (see the examples in Taming Arduino Strings ) This is one of the things that makes using Strings on AVR boards so safe. Strings also automatically protect against overallocation of the heap without causing your program to crash.

The AVR (and others) memory manager also coalesces adjacent empty fragments to improve reuse.
The managing arduino memory reference talks about memory corruption, but that is not possible on AVR boards UNLESS you try and allocate more then 128bytes of stack in method call.

Memory corruption is possible (and happens) on ESP2866 and ESP23 boards which do not have the 128byte stack buffer.

Allocating lots of small Strings when combined with returning Strings from functions does product memory fragmentation. ESP boards have a 12 char[ ] build into their String class which they use for 'small' Strings to avoid allocating memory on the heap for small Strings.

So in summary, dynamic memory allocation on AVR boards is 'safe' if you ALWAYS check the return and handle out-of-memory AND don't use more then 128bytes of local stack in your methods (including arguments).

Freeing memory after you are finished with it (and not before) will make your memory go further.

The biggest problem (in my mind) using dynamic memory allocation is what to do if/when your program runs out of memory. How does your program recover? The Arduino String library just returns an empty String and keeps going. Your program won't crash but the logic may be screwy.
Using your own dynamic memory, your program has to cope with null pointers in some 'safe' way. This tends to lead to a lot of is pointer null checks.