RobertWoodruff 7 Posted May 23, 2016 Share Posted May 23, 2016 This is the substring method of the String library in Energia. 1 String String::substring(unsigned int left, unsigned int right) const 2 { 3 if (left > right) { 4 unsigned int temp = right; 5 right = left; 6 left = temp; 7 } 8 String out; 9 if (left >= len) return out; 10 if (right > len) right = len; 11 char temp = buffer ; // save the replaced character12 buffer = '\0'; 13 out = buffer + left; // pointer arithmetic 14 buffer = temp; //restore character15 return out; 16 } Line 12: That seems to set the right character to 0 rather than bringing it into the substring, right? Line 8: Unsure I understand how memory and object management works in this world and its libraries. Looks like it would allocate space for the object Quote Link to post Share on other sites
spirilis 1,265 Posted May 23, 2016 Share Posted May 23, 2016 Line 12 does set the right character to 0, but then after the substring is copied into "out", it's restored (see line 14). I'm actually not sure about the memory management aspects here, particularly of line 8. I would suspect this allocates from the stack which would corrupt after exit, but that might not be the case. Have you experienced any issues with this library? Quote Link to post Share on other sites
RobertWoodruff 7 Posted May 23, 2016 Author Share Posted May 23, 2016 The char set to 0 is restored to the original buffer rather than the new buffer. So the new buffer does not get the right most character. Kind of feels like a bug? Quote Link to post Share on other sites
spirilis 1,265 Posted May 23, 2016 Share Posted May 23, 2016 The char set to 0 is restored to the original buffer rather than the new buffer. So the new buffer does not get the right most character. Kind of feels like a bug? I think it depends on whether right is meant to be inclusive or not. I suspect it's not meant to be inclusive, hence why it works this way. So the substring comprises left to (right-1). That's pretty common in other programming languages IIRC. tripwire 1 Quote Link to post Share on other sites
RobertWoodruff 7 Posted May 23, 2016 Author Share Posted May 23, 2016 That sounds reasonable, not inclusive, Certainty easy enough to use it that way. I usually think of the parameters of substring to be ::substring(start, length). In any event, thank you to the person(s) that wrote this library. It is most useful!! Quote Link to post Share on other sites
tripwire 139 Posted May 23, 2016 Share Posted May 23, 2016 Line 12: That seems to set the right character to 0 rather than bringing it into the substring, right? Line 8: Unsure I understand how memory and object management works in this world and its libraries. Looks like it would allocate space for the object spirilis and oPossum 2 Quote Link to post Share on other sites
RobertWoodruff 7 Posted May 24, 2016 Author Share Posted May 24, 2016 Hi tripwire, Thank for for the explain. Programming on the TI MCU has brought me back to C++ for the first time in quite a long while (mostly use Java and its JRE on larger platforms). It is interesting getting reacquainted with C++ memory management. Thanks! Quote Link to post Share on other sites
RobertWoodruff 7 Posted May 24, 2016 Author Share Posted May 24, 2016 Hi, again, tripwire, Let me ask, what is the difference between these two statements 1. String aStr = String("abc"); 2. String *aStr = new String("abc"); Is this this: In stmt 1 when aStr goes out of scope ~String is called and the object is deleted/freed. In stmt 2 when aStr goes out of scope the object remains allocated and no destructor call? Quote Link to post Share on other sites
tripwire 139 Posted May 24, 2016 Share Posted May 24, 2016 Let me ask, what is the difference between these two statements 1. String aStr = String("abc"); 2. String *aStr = new String("abc"); Is this this: In stmt 1 when aStr goes out of scope ~String is called and the object is deleted/freed. In stmt 2 when aStr goes out of scope the object remains allocated and no destructor call? Pretty much, yes. For statement 1 I wouldn't say the object is deleted/freed because it was never new'd or malloc'd, but it does get destroyed by the implicit call to ~String(). For information, statement 1 could be written as just "String aStr("abc");", which has the same end result but avoids constructing a temporary string and then copying it to aStr. I wasn't too sure whether the compiler would perform this optimisation itself, so I checked wikipedia: https://en.wikipedia.org/wiki/Copy_elision. It looks like this case is commonly optimised, but writing String aStr = String("abc"); does mean that String needs an accessible copy constructor to compile successfully. Quote Link to post Share on other sites
roadrunner84 466 Posted May 24, 2016 Share Posted May 24, 2016 @@RobertWoodruff The trick at line 12 has to be viewed in combination with line 13 and 14: 12 buffer[right] = '\0'; 13 out = buffer + left; // pointer arithmetic 14 buffer[right] = temp; //restore character As you can see, the character at index right is replaced by '\0' which is the string termination character. By result the string is now cropped from the right. Then the string is copied by the assignment operator of the String class. Last the termination character is yet again replaced by the original character. Now this may seem involved, since this would have the same result 12 // remove this line 13 out = buffer + left; // pointer arithmetic 14 out[right-left] = '\0'; However, consider this call String org = "The quick brown fox jumped over the lazy dog."; String res; res = org.substring(4,9); // make res become "quick" In the last code snippet, the out variable would first become "quick brown fox jumped over the lazy dog." and then be cropped to "quick". So out needs much more memory, and much more data needs to be copied. By cropping the source from the right first, out needs much less space and copying is much faster. Quote Link to post Share on other sites
Rickta59 589 Posted May 24, 2016 Share Posted May 24, 2016 Just because you put the String object on the stack that doesn' t mean the managed string is on the stack. In fact using the code above you end up making multiple calls to new (really malloc in Energias case) Breakpoint 1, setup () at /tmp/build7151962017509443788.tmp/sketch_may24a.cpp:2 2 void setup(); (gdb) b malloc Breakpoint 2 at 0xe6da: file ./stdlib/malloc.c, line 32. (gdb) c Continuing. Breakpoint 2, malloc (size=11) at ./stdlib/malloc.c:32 32 ./stdlib/malloc.c: No such file or directory. in ./stdlib/malloc.c (gdb) where #0 malloc (size=11) at ./stdlib/malloc.c:32 Reading 64 bytes from 0x02c0 #1 0x0000e2aa in String::changeBuffer (this=0x2f8, maxStrLen=10) at /mnt/vbox/shared/github/Energia/build/linux/work/hardware/msp430/cores/msp430/WString.cpp:159 #2 0x0000e2ec in String::reserve (this=0x2f8, size=<value optimized out>) at /mnt/vbox/shared/github/Energia/build/linux/work/hardware/msp430/cores/msp430/WString.cpp:150 Reading 8 bytes from 0xe7dc Reading 8 bytes from 0xe7e4 #3 0x0000e31a in String::copy (this=0x2f8, cstr=0xe7dc "0123456789", length=10) at /mnt/vbox/shared/github/Energia/build/linux/work/hardware/msp430/cores/msp430/WString.cpp:177 #4 0x0000e35e in String::String (this=<value optimized out>, cstr=<value optimized out>) at /mnt/vbox/shared/github/Energia/build/linux/work/hardware/msp430/cores/msp430/WString.cpp:33 #5 0x0000e098 in setup () at /tmp/build7151962017509443788.tmp/sketch_may24a.cpp:6 #6 0x0000e05a in main () at /mnt/vbox/shared/github/Energia/build/linux/work/hardware/msp430/cores/msp430/main.cpp:7 (gdb) c Breakpoint 2, malloc (size=1) at ./stdlib/malloc.c:32 32 in ./stdlib/malloc.c (gdb) c Breakpoint 2, malloc (size=5) at ./stdlib/malloc.c:32 32 in ./stdlib/malloc.c As you can see it made at least 3 calls to malloc 11 bytes, 1 byte, and 5 bytes. The 11, 1, and 5 are requested by the String class. Although the String class data is going to be put on the stack, that only contains the meta data, like the buffer address, its capacity and len: (gdb) p this $5 = (const String * const) 0x2f8 String foo = {buffer = 0x240 "0123456789", capacity = 10, len = 10} The actual allocated buffer comes from heap managed memory, note the address of 0x240 which is much lower address than the address of the String foo metadata living at 0x2f8. The malloced data represents the actual buffer content length plus a null terminator byte. So 11 bytes 10+1 null and 5 bytes (4 + 1 null). In the string class the line: String out; ends up allocating 1 byte because an initial value isn't provided so it defaults to "" + null. In all honesty, you want to avoid using the String class as it is going to eventually fragment memory and fail. It is a horrible thing to use on chips with hardly any memory. -rick BTW: Here is the test code I used: void setup(){ // put your setup code here, to run once: Serial.begin(9600); String foo("0123456789"); String bar = foo.substring(1,5); Serial.println(bar); } void loop(){ // put your main code here, to run repeatedly: } tripwire, oPossum, spirilis and 1 other 4 Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.