Jump to content
43oh

Question of memory and C++ object allocation


Recommended Posts

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 character

12    buffer

= '\0';  

13    out = buffer + left;  // pointer arithmetic

14    buffer

= temp;  //restore character

15    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

Link to post
Share on other sites

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?

Link to post
Share on other sites

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.

Link to post
Share on other sites

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!

Link to post
Share on other sites

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?

Link to post
Share on other sites

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.

Link to post
Share on other sites

@@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.

Link to post
Share on other sites

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:

}

 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...