1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-27 18:02:17 +03:00

exceptions: optionally enforce c++ standards (#6333)

* exceptions: 3 choices: legacy, std::new never returns 0, or exceptions enabled
* arduino_new (doc, example, array)
This commit is contained in:
david gauchard
2019-08-29 00:21:10 +02:00
committed by GitHub
parent 0a031ce957
commit 85f1ea7c78
6 changed files with 420 additions and 65 deletions

View File

@ -215,3 +215,75 @@ using FPSTR would become...
String response2;
response2 += FPSTR(HTTP);
}
C++
----
- About C++ exceptions, ``operator new``, and Exceptions menu option
The C++ standard says the following about the ``new`` operator behavior when encountering heap shortage (memory full):
- has to throw a ``std::bad_alloc`` C++ exception when they are enabled
- will ``abort()`` otherwise
There are several reasons for the first point above, among which are:
- guarantee that the return of new is never a ``nullptr``
- guarantee full construction of the top level object plus all member subobjects
- guarantee that any subobjects partially constructed get destroyed, and in the correct order, if oom is encountered midway through construction
When C++ exceptions are disabled, or when using ``new(nothrow)``, the above guarantees can't be upheld, so the second point (``abort()``) above is the only ``std::c++`` viable solution.
Historically in Arduino environments, ``new`` is overloaded to simply return the equivalent ``malloc()`` which in turn can return ``nullptr``.
This behavior is not C++ standard, and there is good reason for that: there are hidden and very bad side effects. The *class and member constructors are always called, even when memory is full* (``this == nullptr``).
In addition, the memory allocation for the top object could succeed, but allocation required for some member object could fail, leaving construction in an undefined state.
So the historical behavior of Ardudino's ``new``, when faced with insufficient memory, will lead to bad crashes sooner or later, sometimes unexplainable, generally due to memory corruption even when the returned value is checked and managed.
Luckily on esp8266, trying to update RAM near address 0 will immediately raise an hardware exception, unlike on other uC like avr on which that memory can be accessible.
As of core 2.6.0, there are 3 options: legacy (default) and two clear cases when ``new`` encounters oom:
- ``new`` returns ``nullptr``, with possible bad effects or immediate crash when constructors (called anyway) initialize members (exceptions are disabled in this case)
- C++ exceptions are disabled: ``new`` calls ``abort()`` and will "cleanly" crash, because there is no way to honor memory allocation or to recover gracefully.
- C++ exceptions are enabled: ``new`` throws a ``std::bad_alloc`` C++ exception, which can be caught and handled gracefully.
This assures correct behavior, including handling of all subobjects, which guarantees stability.
History: `#6269 <https://github.com/esp8266/Arduino/issues/6269>`__ `#6309 <https://github.com/esp8266/Arduino/pull/6309>`__ `#6312 <https://github.com/esp8266/Arduino/pull/6312>`__
- New optional allocator ``arduino_new``
A new optional global allocator is introduced with a different semantic:
- never throws exceptions on oom
- never calls constructors on oom
- returns nullptr on oom
It is similar to arduino ``new`` semantic without side effects
(except when parent constructors, or member constructors use ``new``).
Syntax is slightly different, the following shows the different usages:
.. code:: cpp
// with new:
SomeClass* sc = new SomeClass(arg1, arg2, ...);
delete sc;
SomeClass* scs = new SomeClass[42];
delete [] scs;
// with arduino_new:
SomeClass* sc = arduino_new(SomeClass, arg1, arg2, ...);
delete sc;
SomeClass* scs = arduino_newarray(SomeClass, 42);
delete [] scs;