Ungraceful handling of heap depletion

I have the Arduino Due sam3x8e.

I've written code to process a command called "HEAP_ALLOC" over the RS232 interface, as follows:

bool RS232_HeapAlloc::Execute(Command const *const cmd)
{
        uint16_t const bytes = cmd->GetParameter().toUInt16();

        if ( nullptr == new(std::nothrow) char[bytes] )
        {
            return false;
        }

        return true;
}

So as you can see, this RS232 command just allocates memory on the heap, and returns 'false' if the allocation fails.

So I also have another RS232 command called "CAPACITY?" that prints out the RAM usage, the code for which I took from the user 'ard_newbie' here on this forum. I adapted the following code:

#include <malloc.h>

extern char _end;
extern "C" char *sbrk(int i);
char *ramstart = (char *)0x20070000;
char *ramend = (char *)0x20088000;

void setup() {
  Serial.begin(250000);
  
  char *heapend = sbrk(0);
  register char * stack_ptr asm ("sp");
  struct mallinfo mi = mallinfo();
  printf("\nDynamic ram used: %d\n", mi.uordblks);
  printf("Program static ram used %d\n", &_end - ramstart);
  printf("Stack ram used %d\n\n", ramend - stack_ptr);
  printf("My guess at free mem: %d\n", stack_ptr - heapend + mi.fordblks);
}

When I first boot up my microcontroller and issue the "CAPACITY?" command over RS232, here's how the figures look:

Dynamic RAM used: 3.7 kB
Program static Flash used: 25.4 kB
Stack RAM used: 0.2 kB
My guess at free mem: 66.5 kB

If I then send the RS232 command "HEAP_ALLOC,70000" to try allocate 70 kB, it fails and gives an error (as expected). I then check the above figures again and they haven't changed.

Next I issue "HEAP_ALLOC,35000" and it succeeds, and I check the figures again and here's how they look:

Dynamic RAM used: 37.9 kB
Program static Flash used: 25.4 kB
Stack RAM used: 0.2 kB
My guess at free mem: 32.3 kB

This seems fine. But watch what happens if I now do "HEAP_ALLOC,40000". I would expect it to fail because there isn't enough heap available, however it does not report an error, it behaves as though it succeeded. And then when I check the figures, I see:

Dynamic RAM used: 77.9 kB
Program static Flash used: 25.4 kB
Stack RAM used: 0.2 kB
My guess at free mem: 4194297.2 kB

I can keep issuing the command "HEAP_ALLOC,40000" and it never reports an error, and the "Dynamic RAM used" just climbs and climbs and climbs.

If I power the microcontroller on and do the following sequence:

Microcontroller boots up
HEAP_ALLOC,70000  (this fails)
HEAP_ALLOC,35000
HEAP_ALLOC,40000
HEAP_ALLOC,40000
HEAP_ALLOC,40000
HEAP_ALLOC,40000

then here's how the figures look:

Dynamic RAM used: 194.2 kB
Program static Flash used: 25.4 kB
Stack RAM used: 0.2 kB
My guess at free mem: 4194180.2 kB

So here's what I'm thinking:

(Point No. 1) If you try to dynamically allocate N bytes of memory, where N is > total_size_of_heap, then it always fails, and the 'new' operator appropriately returns a nullptr.
(Point No. 2) If you try to dynamically allocate N bytes of memory, where bytes_of_heap_available > N > total_size_of_heap, then it behaves as though it has successfully allocated the memory even though it hasn't.

Anybody got any thoughts on this?

What happens if you use malloc() instead of new?

For correct results you should add (int) qualifier to this line, as stated in the thread where you got it from

printf("My guess at free mem: %d\n", (int) (stack_ptr - heapend + mi.fordblks));`

I use uintmax_t in my code like this:

    void AddKilobytesToResponse(std::uintmax_t const arg)
    {
		static char str[64u];  // Max length = 39 decimal digits (2^128-1 == 340282366920938463463374607431768211455)
		std::sprintf(str,"%" PRIuMAX ".", static_cast<std::uintmax_t>(arg / 1024u));
		response += str;
		std::sprintf(str,"%" PRIuMAX " kB", static_cast<std::uintmax_t>((arg % 1024u) * 0.9765625L / 100.0L));
		response += str;
    }

I have changed 'new' to 'malloc' but this change has no effect -- it continues to report no error when I keep allocating more and more memory (even though the heap's depleted).
I was able to find a problem though by checking the pointer arithmetic:

	cmd->AddStringToResponse("\nMy guess at free mem: ");
	std::ptrdiff_t const diff = stack_ptr - heapend;
	if ( diff >= 0 ) cmd->AddKilobytesToResponse(numeric_cast<uintmax_t>(diff) + mi.fordblks); // ptrdiff_t with positive value converts to uintmax_t
	            else cmd->AddStringToResponse(" (POINTER ARITHMETIC PROBLEM) ");

What's happening here is that stack_ptr is less than heapend, and so diff has a negative value, giving me this:

Dynamic RAM used: 194.2 kB
Program static Flash used: 25.4 kB
Stack RAM used: 0.2 kB
My guess at free mem: (POINTER ARITHMETIC PROBLEM)

So the value returned from "sbrk(0)" seems to just be increasing past the stack pointer. So it looks like there's no bounds-checking here... both "new" and "malloc" just increment and increment and increment.

I concur. _sbrk is actually an arduino-provided function, and it's broken (has no checks at all!)

I have submitted an "issue": _sbrk has no bounds checking · Issue #138 · arduino/ArduinoCore-sam · GitHub

I can't find the source code (be it C++ code or ARM assembler) for the function "malloc" in that repository. Where is it?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.