However, if the statement String s = f();
is replaced by
String s; s = f();
, then the string is copied when returned to the
caller, which doubles the amount of RAM required to handle it. This is
presumably because only the first form allows copy elision
optimization copy elision
optimization. (This
paragraph was added after reading Nick Gammon's answer, which made me
realize the copy elision issue).
However, if the statement String s = f();
is replaced by
String s; s = f();
, then the string is copied when returned to the
caller, which doubles the amount of RAM required to handle it. This is
presumably because only the first form allows copy elision
optimization. (This
paragraph was added after reading Nick Gammon's answer, which made me
realize the copy elision issue).
However, if the statement String s = f();
is replaced by
String s; s = f();
, then the string is copied when returned to the
caller, which doubles the amount of RAM required to handle it. This is
presumably because only the first form allows copy elision
optimization. (This
paragraph was added after reading Nick Gammon's answer, which made me
realize the copy elision issue).
- 45.1k
- 4
- 42
- 81
extern uintptr_t __brkval;
String f() {
String s("");
const char * p = s.c_str();
Serial.print(F(" f(): string buffer @ 0x"));
Serial.println((uintptr_t) p, 16);
for (int i = 0; i < random(8) + 1; i++)
s += random(2) ? "a": "bc";
if (p != s.c_str())
Serial.println(F("Warning: memory fragmentation."));
Serial.print(F(" top of heap at 0x"));
Serial.println(__brkval, 16);
return s;
}
void setup() {
Serial.begin(9600);
Serial.print(F("*** heap starts at 0x"));
Serial.println((uintptr_t) __malloc_heap_start, 16);
}
void loop() {
Serial.print(F("loop(): top of heap at 0x"));
Serial.println(__brkval, 16);
String s = f();
Serial.print(F(" f() returned "));
Serial.println(s);
Serial.print(F(" with buffer at 0x"));
Serial.println((uintptr_t) s.c_str(), 16);
delay(20001000);
}
*** heap starts at 0x1D7
loop(): top of heap at 0x0
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
with buffer at 0x1D9
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
with buffer at 0x1D9
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DC
f() returned aa
with buffer at 0x1D9
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1E2
f() returned aabcabca
with buffer at 0x1D9
[...]
- the heap is always empty (top = bottom) at the beginning of
loop()
- the string is always allocated at the same place
- the heap size inside
f()
varies depending on the string's length - the string is not copied when returned to the caller, as the internal buffer is still at the same address.
However, if the statement String s = f();
is replaced by
String s; s = f();
, then the string is copied when returned to the
caller, which doubles the amount of RAM required to handle it. This is
presumably because only the first form allows copy elision
optimization . (This
paragraph was added after reading Nick Gammon's answer, which made me
realize the copy elision issue).
extern uintptr_t __brkval;
String f() {
String s("");
const char * p = s.c_str();
Serial.print(F(" f(): string buffer @ 0x"));
Serial.println((uintptr_t) p, 16);
for (int i = 0; i < random(8) + 1; i++)
s += random(2) ? "a": "bc";
if (p != s.c_str())
Serial.println(F("Warning: memory fragmentation."));
Serial.print(F(" top of heap at 0x"));
Serial.println(__brkval, 16);
return s;
}
void setup() {
Serial.begin(9600);
Serial.print(F("*** heap starts at 0x"));
Serial.println((uintptr_t) __malloc_heap_start, 16);
}
void loop() {
Serial.print(F("loop(): top of heap at 0x"));
Serial.println(__brkval, 16);
String s = f();
Serial.print(F(" f() returned "));
Serial.println(s);
delay(2000);
}
*** heap starts at 0x1D7
loop(): top of heap at 0x0
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DC
f() returned aa
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1E2
f() returned aabcabca
[...]
- the heap is always empty (top = bottom) at the beginning of
loop()
- the string is always allocated at the same place
- the heap size inside
f()
varies depending on the string's length.
extern uintptr_t __brkval;
String f() {
String s("");
const char * p = s.c_str();
Serial.print(F(" f(): string buffer @ 0x"));
Serial.println((uintptr_t) p, 16);
for (int i = 0; i < random(8) + 1; i++)
s += random(2) ? "a": "bc";
if (p != s.c_str())
Serial.println(F("Warning: memory fragmentation."));
Serial.print(F(" top of heap at 0x"));
Serial.println(__brkval, 16);
return s;
}
void setup() {
Serial.begin(9600);
Serial.print(F("*** heap starts at 0x"));
Serial.println((uintptr_t) __malloc_heap_start, 16);
}
void loop() {
Serial.print(F("loop(): top of heap at 0x"));
Serial.println(__brkval, 16);
String s = f();
Serial.print(F(" f() returned "));
Serial.println(s);
Serial.print(F(" with buffer at 0x"));
Serial.println((uintptr_t) s.c_str(), 16);
delay(1000);
}
*** heap starts at 0x1D7
loop(): top of heap at 0x0
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
with buffer at 0x1D9
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
with buffer at 0x1D9
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DC
f() returned aa
with buffer at 0x1D9
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1E2
f() returned aabcabca
with buffer at 0x1D9
[...]
- the heap is always empty (top = bottom) at the beginning of
loop()
- the string is always allocated at the same place
- the heap size inside
f()
varies depending on the string's length - the string is not copied when returned to the caller, as the internal buffer is still at the same address.
However, if the statement String s = f();
is replaced by
String s; s = f();
, then the string is copied when returned to the
caller, which doubles the amount of RAM required to handle it. This is
presumably because only the first form allows copy elision
optimization . (This
paragraph was added after reading Nick Gammon's answer, which made me
realize the copy elision issue).
Edit: I wrote this small test program to show the behaviour of the heap when you use string concatenation this way:
extern uintptr_t __brkval;
String f() {
String s("");
const char * p = s.c_str();
Serial.print(F(" f(): string buffer @ 0x"));
Serial.println((uintptr_t) p, 16);
for (int i = 0; i < random(8) + 1; i++)
s += random(2) ? "a": "bc";
if (p != s.c_str())
Serial.println(F("Warning: memory fragmentation."));
Serial.print(F(" top of heap at 0x"));
Serial.println(__brkval, 16);
return s;
}
void setup() {
Serial.begin(9600);
Serial.print(F("*** heap starts at 0x"));
Serial.println((uintptr_t) __malloc_heap_start, 16);
}
void loop() {
Serial.print(F("loop(): top of heap at 0x"));
Serial.println(__brkval, 16);
String s = f();
Serial.print(F(" f() returned "));
Serial.println(s);
delay(2000);
}
The output of the program is:
*** heap starts at 0x1D7
loop(): top of heap at 0x0
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DC
f() returned aa
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1E2
f() returned aabcabca
[...]
The very first "top of heap at" is bogus because at this point the malloc library has not been initialized. Besides that, you can see here that:
- the heap is always empty (top = bottom) at the beginning of
loop()
- the string is always allocated at the same place
- the heap size inside
f()
varies depending on the string's length.
Keep in mind that this works so nicely only because there is nothing else doing dynamic allocation in this small program. This usage pattern is safe, but once you start doing dynamic allocation in different parts of the program, it becomes difficult to ensure that the allocation/deallocation pattern is still safe vs. memory fragmentation.
Edit: I wrote this small test program to show the behaviour of the heap when you use string concatenation this way:
extern uintptr_t __brkval;
String f() {
String s("");
const char * p = s.c_str();
Serial.print(F(" f(): string buffer @ 0x"));
Serial.println((uintptr_t) p, 16);
for (int i = 0; i < random(8) + 1; i++)
s += random(2) ? "a": "bc";
if (p != s.c_str())
Serial.println(F("Warning: memory fragmentation."));
Serial.print(F(" top of heap at 0x"));
Serial.println(__brkval, 16);
return s;
}
void setup() {
Serial.begin(9600);
Serial.print(F("*** heap starts at 0x"));
Serial.println((uintptr_t) __malloc_heap_start, 16);
}
void loop() {
Serial.print(F("loop(): top of heap at 0x"));
Serial.println(__brkval, 16);
String s = f();
Serial.print(F(" f() returned "));
Serial.println(s);
delay(2000);
}
The output of the program is:
*** heap starts at 0x1D7
loop(): top of heap at 0x0
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DF
f() returned abcbc
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1DC
f() returned aa
loop(): top of heap at 0x1D7
f(): string buffer @ 0x1D9
top of heap at 0x1E2
f() returned aabcabca
[...]
The very first "top of heap at" is bogus because at this point the malloc library has not been initialized. Besides that, you can see here that:
- the heap is always empty (top = bottom) at the beginning of
loop()
- the string is always allocated at the same place
- the heap size inside
f()
varies depending on the string's length.
Keep in mind that this works so nicely only because there is nothing else doing dynamic allocation in this small program. This usage pattern is safe, but once you start doing dynamic allocation in different parts of the program, it becomes difficult to ensure that the allocation/deallocation pattern is still safe vs. memory fragmentation.