How Should Teensy Respond if Dynamic Memory Can't be Allocated?

gfvalvo

Well-known member
It's a best practice to check if a request for dynamic memory allocation succeeded or failed. So, trying with a T3.2:

Using malloc() I get the expected result if there's insufficient memory:
Code:
#include "Arduino.h"

void setup() {
  Serial.begin(115200);

  char *ptr = malloc(10000000);
  Serial.printf("0x%08x: ", (uint32_t) ptr);
  if (ptr != nullptr) {
    Serial.println("Good Pointer");
  } else {
    Serial.println("Null Pointer");
  }
}

void loop() {
}
Result (expected):
Code:
0x00000000: Null Pointer


But, trying it with "new[]":
Code:
#include "Arduino.h"

void setup() {
  Serial.begin(115200);

  char *ptr = new char[10000000];
  Serial.printf("0x%08x: ", (uint32_t) ptr);
  if (ptr != nullptr) {
    Serial.println("Good Pointer");
  } else {
    Serial.println("Null Pointer");
  }
}

void loop() {
}
Result (unexpected):
Code:
0x00000000: Good Pointer

Seems rather odd. I'd appreciate if anyone can shed light on what appears to be undefined behavior.
Thanks.

PS: T3.2, Arduino IDE v1.8.15, Teensyduino v1.55
 
New is not supposed to return null (it should throw instead). The optimizer knows that and optimizes your else branch away. Try new (std::nothrow) instead.

Can use use a #define or template or some other way so programs with "new" always use this?
 
I personally wouldn't "re-#define" a documented property of the language. Chances are that it will generate issues elsewhere. I like the idea from jonr/peterP https://forum.pjrc.com/threads/6821...ing-fcheck-new?p=289338&viewfull=1#post289338 to do the check for nullptr in the definition of new and call __throw_bad_alloc() in case malloc returns null.

I can give it a quick try...

Edit: maybe it would even be possible to generate a corresponding crash report ?
 
Maybe my application is non-standard for a C++ program. I'd be happy for 'new' to gracefully return a nullptr and let my code handle it. That's easy with this application, things would keep working with an appropriate limitation and life would go on.
 
Thought I just try "new" on a ESP32:

Code:
[      1][D][esp32-hal-cpu.c:211] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz

abort() was called at PC 0x4016b1af on core 1


Backtrace:0x400841f5:0x3ffb26900x4008d749:0x3ffb26b0 0x400930cd:0x3ffb26d0 0x4016b1af:0x3ffb2750 0x4016b1f6:0x3ffb2770 0x4016afe3:0x3ffb2790 0x4016ab0a:0x3ffb27b0 0x4016b03d:0x3ffb27d0 0x400d333b:0x3ffb27f0 0x400ec826:0x3ffb2820
  #0  0x400841f5:0x3ffb26900 in panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:402

It just calls abort() ( panic_abort) without giving additional info, if there is not enough room.
At least, it's not completely silent.

std::nothrow works good!. Thank you for the hint.

Code:
[COLOR=#d4d4d4][FONT=Consolas][COLOR=#569cd6]int[/COLOR][COLOR=#d4d4d4] *[/COLOR][COLOR=#9cdcfe]x[/COLOR][COLOR=#d4d4d4] = [/COLOR][COLOR=#c586c0]new[/COLOR][COLOR=#d4d4d4] ([/COLOR][COLOR=#4ec9b0]std[/COLOR][COLOR=#d4d4d4]::[/COLOR][COLOR=#9cdcfe]nothrow[/COLOR][COLOR=#d4d4d4]) [/COLOR][COLOR=#569cd6]int[/COLOR][COLOR=#d4d4d4][[/COLOR][COLOR=#b5cea8]102400[/COLOR][COLOR=#d4d4d4]];[/COLOR]
[COLOR=#c586c0]if[/COLOR][COLOR=#d4d4d4] ([/COLOR][COLOR=#9cdcfe]x[/COLOR][COLOR=#d4d4d4] == [/COLOR][COLOR=#569cd6]nullptr[/COLOR][COLOR=#d4d4d4]) {[/COLOR]
  [COLOR=#569cd6]log_e[/COLOR][COLOR=#d4d4d4]([/COLOR][COLOR=#ce9178]"NEW: Out of memory"[/COLOR][COLOR=#d4d4d4]);[/COLOR]
[/FONT][/COLOR]


gives a nice red errormessage:
Code:
[     1][D][esp32-hal-cpu.c:211] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz
[COLOR=#ff0000][    40][E][main.cpp:240] setup(): NEW: Out of memory
[/COLOR]


 
Last edited:
I did the following change in new.cpp

Code:
namespace std
{
#if !defined(__throw_bad_alloc)
    __attribute__((weak)) void __throw_bad_alloc()
    {
        // for testing:
        Serial.println("bad alloc");
        while (true)
        {
            // maybe generate a crash report?
        }
    }
#endif
}

void* operator new(size_t size)
{
    void* p = malloc(size);
    if(!p) std::__throw_bad_alloc();
    return p;
    //return malloc(size);
}

void* operator new[](size_t size)
{
    void* p = malloc(size);
    if(!p) std::__throw_bad_alloc();
    return p;
    //return malloc(size);
}

This works of course. Would be nice to generate a crash report in this case. Defining it weak would allow users to define their own handler. Calling abort instead would of course work also. If I'd want to check for null instead, I'd use the correct std::nowthrow which explicitly shows the purpose.
 
TD does not allow to generate crash reports with custom texts.
I proposed that, and my original crash report supported that and even had a "assert", but it seems such messages are not wanted.
 
If I'd want to check for null instead, I'd use the correct 'std::nowthrow' which explicitly shows the purpose.
Yea. Unfortunately, 'std::make_unique' appears to call the "regular" new. So, that makes it a two-step process:
1. Use 'new (std::nothrow)' to allocate memory and check for null.
2. If the returned pointer is non-null, use it to make a 'std::unique_ptr'.
 
Yes, library functions often rely on new throwing. And if the caller is interested he is supposed to catch the exception... I'm afraid there is not much what can be done (at least I don't have an idea). I kind of remember that the smart pointers also accept custom allocators, maybe that helps here. I'll see if I find some information.
 
Yea. Unfortunately, 'std::make_unique' appears to call the "regular" new. So, that makes it a two-step process:
1. Use 'new (std::nothrow)' to allocate memory and check for null.
2. If the returned pointer is non-null, use it to make a 'std::unique_ptr'.

@What about this:
Code:
#include "Arduino.h"
#include <memory>

void setup()
{
  while(!Serial){}

  //std::unique_ptr<int[]> p(new (std::nothrow) int[1'000]);     // good
  std::unique_ptr<int[]> p(new (std::nothrow) int[1'000'000]); // bad

  Serial.printf("%s\n", p.get() == nullptr ? "bad" : "good");
}

void loop(){
}

(remark. IIRC c-arrays are not a good idea in c++14 for a unique pointer since it will call delete instead of the correct delete[] but I might be wrong here)
 
Last edited:
> Can use use a #define or template or some other way ...

+1 on wanting a way to select between two different versions of new(). If (std::nothrow) isn't specified, then new() should abort on malloc failure. No idea how to do this kind of conditional compilation.

If there is no solution, at least document the issue and make new() weak.

For now, all teensy code using new should use new(std::nothrow) and check for null. Looking at various library code, this isn't done.
 
Interesting, it looks like one can independently overload new when used with and without the std::nothrow option.

void* operator new(std::size_t size) noexcept {
// malloc and abort if it fails
}

vs
void* operator new(std::size_t size, const std::nothrow_t& t) noexcept {
// return malloc()
}
 
This still seems like the way to go for doing something predictable when new is used without the recommended '(std::nothrow)' and a null check.

Here is code that compiles in new.cpp (but isn't tested). The same should be done for new[].

Code:
#include <stdlib.h>
#include "new"

void* operator new(size_t size, const std::nothrow_t& t)  {  
        return malloc(size);
} 

void * operator new(size_t size)
{
    void* p = malloc(size);
    if (!p) 
       for (;;);    // do something better?
    return p;
}
 
Back
Top