Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 85f1ea7

Browse filesBrowse files
authored
exceptions: optionally enforce c++ standards (#6333)
* exceptions: 3 choices: legacy, std::new never returns 0, or exceptions enabled * arduino_new (doc, example, array)
1 parent 0a031ce commit 85f1ea7
Copy full SHA for 85f1ea7

File tree

Expand file treeCollapse file tree

6 files changed

+420
-65
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+420
-65
lines changed

‎boards.txt

Copy file name to clipboardExpand all lines: boards.txt
+150-60Lines changed: 150 additions & 60 deletions
Large diffs are not rendered by default.

‎cores/esp8266/abi.cpp

Copy file name to clipboardExpand all lines: cores/esp8266/abi.cpp
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));
3232
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
3333

3434

35-
#ifndef __cpp_exceptions
35+
#if !defined(__cpp_exceptions) && !defined(NEW_OOM_ABORT)
3636
void *operator new(size_t size)
3737
{
3838
void *ret = malloc(size);
@@ -52,7 +52,7 @@ void *operator new[](size_t size)
5252
}
5353
return ret;
5454
}
55-
#endif
55+
#endif // arduino's std::new legacy
5656

5757
void __cxa_pure_virtual(void)
5858
{

‎cores/esp8266/core_esp8266_features.h

Copy file name to clipboardExpand all lines: cores/esp8266/core_esp8266_features.h
+32-1Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,37 @@
3232

3333
#define WIFI_HAS_EVENT_CALLBACK
3434

35+
#ifdef __cplusplus
36+
37+
#include <stdlib.h> // malloc()
38+
#include <stddef.h> // size_t
39+
40+
namespace arduino
41+
{
42+
extern "C++"
43+
template <typename T, typename ...TConstructorArgs>
44+
T* new0 (size_t n, TConstructorArgs... TconstructorArgs)
45+
{
46+
// n==0: single allocation, otherwise it is an array
47+
size_t offset = n? sizeof(size_t): 0;
48+
size_t arraysize = n? n: 1;
49+
T* ptr = (T*)malloc(offset + (arraysize * sizeof(T)));
50+
if (ptr)
51+
{
52+
if (n)
53+
*(size_t*)(ptr) = n;
54+
for (size_t i = 0; i < arraysize; i++)
55+
new (ptr + offset + i * sizeof(T)) T(TconstructorArgs...);
56+
return ptr + offset;
57+
}
58+
return nullptr;
59+
}
60+
}
61+
62+
#define arduino_new(Type, ...) arduino::new0<Type>(0, ##__VA_ARGS__)
63+
#define arduino_newarray(Type, n, ...) arduino::new0<Type>(n, ##__VA_ARGS__)
64+
65+
#endif // __cplusplus
3566

3667
#ifndef __STRINGIFY
3768
#define __STRINGIFY(a) #a
@@ -61,4 +92,4 @@ inline uint32_t esp_get_cycle_count() {
6192
}
6293
#endif // not CORE_MOCK
6394

64-
#endif
95+
#endif // CORE_ESP8266_FEATURES_H

‎doc/reference.rst

Copy file name to clipboardExpand all lines: doc/reference.rst
+72Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,75 @@ using FPSTR would become...
215215
String response2;
216216
response2 += FPSTR(HTTP);
217217
}
218+
219+
C++
220+
----
221+
222+
- About C++ exceptions, ``operator new``, and Exceptions menu option
223+
224+
The C++ standard says the following about the ``new`` operator behavior when encountering heap shortage (memory full):
225+
226+
- has to throw a ``std::bad_alloc`` C++ exception when they are enabled
227+
228+
- will ``abort()`` otherwise
229+
230+
There are several reasons for the first point above, among which are:
231+
232+
- guarantee that the return of new is never a ``nullptr``
233+
234+
- guarantee full construction of the top level object plus all member subobjects
235+
236+
- guarantee that any subobjects partially constructed get destroyed, and in the correct order, if oom is encountered midway through construction
237+
238+
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.
239+
240+
Historically in Arduino environments, ``new`` is overloaded to simply return the equivalent ``malloc()`` which in turn can return ``nullptr``.
241+
242+
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``).
243+
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.
244+
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.
245+
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.
246+
247+
As of core 2.6.0, there are 3 options: legacy (default) and two clear cases when ``new`` encounters oom:
248+
249+
- ``new`` returns ``nullptr``, with possible bad effects or immediate crash when constructors (called anyway) initialize members (exceptions are disabled in this case)
250+
251+
- C++ exceptions are disabled: ``new`` calls ``abort()`` and will "cleanly" crash, because there is no way to honor memory allocation or to recover gracefully.
252+
253+
- C++ exceptions are enabled: ``new`` throws a ``std::bad_alloc`` C++ exception, which can be caught and handled gracefully.
254+
This assures correct behavior, including handling of all subobjects, which guarantees stability.
255+
256+
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>`__
257+
258+
- New optional allocator ``arduino_new``
259+
260+
A new optional global allocator is introduced with a different semantic:
261+
262+
- never throws exceptions on oom
263+
264+
- never calls constructors on oom
265+
266+
- returns nullptr on oom
267+
268+
It is similar to arduino ``new`` semantic without side effects
269+
(except when parent constructors, or member constructors use ``new``).
270+
271+
Syntax is slightly different, the following shows the different usages:
272+
273+
.. code:: cpp
274+
275+
// with new:
276+
277+
SomeClass* sc = new SomeClass(arg1, arg2, ...);
278+
delete sc;
279+
280+
SomeClass* scs = new SomeClass[42];
281+
delete [] scs;
282+
283+
// with arduino_new:
284+
285+
SomeClass* sc = arduino_new(SomeClass, arg1, arg2, ...);
286+
delete sc;
287+
288+
SomeClass* scs = arduino_newarray(SomeClass, 42);
289+
delete [] scs;
+159Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
2+
// show arduino_new benefits
3+
// released to public domain
4+
// result is below
5+
6+
class SomeClass {
7+
public:
8+
SomeClass(const String& s1 = emptyString, const String& s2 = emptyString) {
9+
Serial.printf("SomeClass@%p(%s)(%s)\n", this, s1.c_str(), s2.c_str());
10+
}
11+
12+
~SomeClass() {
13+
Serial.printf("~ SomeClass @%p\n", this);
14+
}
15+
};
16+
17+
class oom {
18+
private:
19+
char large [65000];
20+
public:
21+
oom() {
22+
Serial.printf("this constructor should not be called\n");
23+
}
24+
};
25+
26+
void setup() {
27+
28+
Serial.begin(115200);
29+
Serial.printf("\n\narduino_new benefits\n\n");
30+
delay(5000); // avoid too frequent bootloop
31+
32+
// arduino_new / arduino_newarray api
33+
34+
Serial.printf("\n----- arduino_new:\n");
35+
auto an = arduino_new(SomeClass);
36+
delete an;
37+
38+
Serial.printf("\n----- arduino_new with oom:\n");
39+
auto anoom = arduino_new(oom);
40+
Serial.printf("nullptr: %p\n", anoom);
41+
delete anoom;
42+
43+
Serial.printf("\n----- arduino_new with constructor parameters:\n");
44+
auto ancp = arduino_new(SomeClass, "param1", "param2");
45+
delete ancp;
46+
47+
Serial.printf("\n----- arduino_newarray[2]\n");
48+
auto ana2 = arduino_newarray(SomeClass, 2);
49+
Serial.printf("@:%p s=%zd s(2)=%zd\n", ana2, sizeof(SomeClass), sizeof(SomeClass[2]));
50+
Serial.printf("0: %p\n", &ana2[0]);
51+
Serial.printf("1: %p\n", &ana2[1]);
52+
delete [] ana2;
53+
54+
Serial.printf("\n----- arduino_newarray[2] (with constructor parameters)\n");
55+
auto ana2cp = arduino_newarray(SomeClass, 2, "param1");
56+
Serial.printf("@:%p s=%zd s(2)=%zd\n", ana2cp, sizeof(SomeClass), sizeof(SomeClass[2]));
57+
Serial.printf("0: %p\n", &ana2cp[0]);
58+
Serial.printf("1: %p\n", &ana2cp[1]);
59+
delete [] ana2cp;
60+
61+
Serial.printf("\n----- arduino_newarray[100000]\n");
62+
auto anaX = arduino_newarray(SomeClass, 100000);
63+
Serial.printf("@:%p\n", anaX);
64+
65+
// standard c++ api for new and new[]
66+
67+
Serial.printf("\n----- new\n");
68+
auto sn = new SomeClass;
69+
delete sn;
70+
71+
Serial.printf("\n----- new with oom: (abort() with option 'Exceptions: Disabled (new can abort)'\n");
72+
auto snoom = new oom;
73+
Serial.printf("nullptr: %p\n", snoom);
74+
delete snoom;
75+
76+
Serial.printf("\n----- new[2]\n");
77+
auto sna2 = new SomeClass[2];
78+
Serial.printf("@:%p s=%zd s(2)=%zd\n", sna2, sizeof(SomeClass), sizeof(SomeClass[2]));
79+
Serial.printf("0: %p\n", &sna2[0]);
80+
Serial.printf("1: %p\n", &sna2[1]);
81+
delete [] sna2;
82+
83+
Serial.printf("\n----- new[10000] (badly fails with 'Exceptions: Legacy' or '...Disabled'\n");
84+
auto snaX = new SomeClass[100000];
85+
Serial.printf("@:%p\n", snaX);
86+
}
87+
88+
void loop() {
89+
}
90+
91+
//////////////////////////////
92+
/*
93+
94+
Result with:
95+
Exceptions: Legacy(new can return nullptr)
96+
97+
//////////////////////////////
98+
99+
arduino_new benefits
100+
101+
----- arduino_new:
102+
SomeClass@0x3fff1864()()
103+
~ SomeClass @0x3fff1864
104+
105+
----- arduino_new with oom:
106+
nullptr: 0
107+
108+
----- arduino_new with constructor parameters:
109+
SomeClass@0x3fff1864(param1)(param2)
110+
~ SomeClass @0x3fff1864
111+
112+
----- arduino_newarray[2]
113+
SomeClass@0x3fff1868()()
114+
SomeClass@0x3fff1869()()
115+
@: 0x3fff1868 s = 1 s(2) = 2
116+
0: 0x3fff1868
117+
1: 0x3fff1869
118+
~ SomeClass @0x3fff1869
119+
~ SomeClass @0x3fff1868
120+
121+
----- arduino_newarray[2](with constructor parameters)
122+
SomeClass@0x3fff1868(param1)()
123+
SomeClass@0x3fff1869(param1)()
124+
@: 0x3fff1868 s = 1 s(2) = 2
125+
0: 0x3fff1868
126+
1: 0x3fff1869
127+
~ SomeClass @0x3fff1869
128+
~ SomeClass @0x3fff1868
129+
130+
----- arduino_newarray[100000]
131+
@: 0
132+
133+
----- new
134+
SomeClass@0x3fff1864()()
135+
~ SomeClass @0x3fff1864
136+
137+
----- new with oom: (abort() with option 'Exceptions: Disabled (new can abort)'
138+
this constructor should not be called
139+
nullptr: 0
140+
141+
----- new[2]
142+
SomeClass@0x3fff1868()()
143+
SomeClass@0x3fff1869()()
144+
@:0x3fff1868 s = 1 s(2) = 2
145+
0: 0x3fff1868
146+
1: 0x3fff1869
147+
~ SomeClass @0x3fff1869
148+
~ SomeClass @0x3fff1868
149+
150+
----- new[10000](badly fails with 'Exceptions: Legacy' or '...Disabled'
151+
152+
Exception(29):
153+
epc1 = 0x402013de epc2 = 0x00000000 epc3 = 0x00000000 excvaddr = 0x00000000 depc = 0x00000000
154+
155+
>>> stack >>>
156+
...
157+
158+
*/
159+
/////////////////////////////

‎tools/boards.txt.py

Copy file name to clipboardExpand all lines: tools/boards.txt.py
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -883,8 +883,11 @@
883883
]),
884884

885885
'exception_menu': collections.OrderedDict([
886-
( '.menu.exception.disabled', 'Disabled' ),
887-
( '.menu.exception.disabled.build.exception_flags', '-fno-exceptions' ),
886+
( '.menu.exception.legacy', 'Legacy (new can return nullptr)' ),
887+
( '.menu.exception.legacy.build.exception_flags', '-fno-exceptions' ),
888+
( '.menu.exception.legacy.build.stdcpp_lib', '-lstdc++' ),
889+
( '.menu.exception.disabled', 'Disabled (new can abort)' ),
890+
( '.menu.exception.disabled.build.exception_flags', '-fno-exceptions -DNEW_OOM_ABORT' ),
888891
( '.menu.exception.disabled.build.stdcpp_lib', '-lstdc++' ),
889892
( '.menu.exception.enabled', 'Enabled' ),
890893
( '.menu.exception.enabled.build.exception_flags', '-fexceptions' ),

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.