https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106477
Bug ID: 106477 Summary: With -fno-exception operator new(nothrow) aborts instead of returning null Product: gcc Version: 11.2.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: matthijs at stdin dot nl Target Milestone: --- The nothrow version of operator new is intended to return null on allocation failure. However, when libstdc++ is compiled with -fno-exceptions, it aborts instead. The cause of this failure is that the nothrow operators work by calling the regular operators, catching any allocation failure exception and turning that into a null return. However, with -fno-exceptions, the regular operator aborts instead of throwing, so the nothrow operator never gets a chance to return null. Originally, this *did* work as expected, because the nothrow operators would just call malloc directly. However, as reported in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68210 this violates the C++11 requirement that the nothrow versions must call the regular versions (so applications can replace the regular version and get the nothrow for free), so this was changed in https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=b66e5a95c0065fda3569a1bfd3766963a848a00d Note this comment by Jonathan Wakely in the linked report, which actually already warns against introducing the behavior I am describing (but the comment was apparently not considered when applying the fix): https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68210#c2 In any case, we have two conflicting requirements: 1. nothrow operators should return null on failure 2. nothrow operators should call regular operators I can see no way to satisfy both. Since -fno-exceptions is already violating the spec, it would make sense to me to, when -fno-exceptions is specified, only satisfy 1 and allow 2 to be violated (which is more of a fringe case anyway, and applications can always replace the nothrow versions too to get the behavior they need). Essentially this would mean that with -fno-exceptions, the nothrow versions would have to call malloc again directly like before (either duplicating code like before, or maybe introducing a null-returning helper function?). To reproduce, I made a small testcase. I was originally seeing this in the Arduino environment on an Atmel samd chip, but I made a self-contained testcase and tested that using gcc from https://developer.arm.com (using the linker script from Atmel/Arduino), which is compiled with -fno-exceptions. The main testcase is simple: An _sbrk() implementation that always fails to force allocation failure (overriding the default libnosys implementation that always succeeds), and a single call to operator new that should return null, but aborts: $ cat test.cpp #include <new> volatile void* foo; extern "C" void *_sbrk(int n) { // Just always fail allocation return (void*)-1; } int main() { // This should return nullptr, but actually aborts (with -fno-exceptions) foo = new (std::nothrow) char[65000]; return 0; } In addition, I added a minimal startup.c for memory initialization and reset vector and a linker script taken verbatim from https://github.com/arduino/ArduinoCore-samd/raw/master/variants/arduino_zero/linker_scripts/gcc/flash_without_bootloader.ld (I will attach both files next). Compiled using: $ ~/Downloads/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb -g -fno-exceptions --specs=nosys.specs --specs=nano.specs -Tflash_without_bootloader.ld -nostartfiles test.cpp startup.c -lstdc++ Running this on the Arduino zero (using openocd and gdb to upload the code through the EDBG port) shows it aborts: Program received signal SIGINT, Interrupt. _exit (rc=rc@entry=1) at /data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/libgloss/libnosys/_exit.c:16 16 /data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/libgloss/libnosys/_exit.c: No such file or directory. (gdb) bt #0 _exit (rc=rc@entry=1) at /data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/libgloss/libnosys/_exit.c:16 #1 0x0000013a in abort () at /data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/newlib/libc/stdlib/abort.c:59 #2 0x00000128 in operator new (sz=65000) at /data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/libstdc++-v3/libsupc++/new_op.cc:54 #3 0x00000106 in operator new[] (sz=<optimized out>) at /data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/libstdc++-v3/libsupc++/new_opv.cc:32 #4 0x000000fe in operator new[] (sz=<optimized out>) at /data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/libstdc++-v3/libsupc++/new_opvnt.cc:38 #5 0x00000034 in main () at test.cpp:17