Sunday, April 21, 2013

Don't let std::stringstream.str().c_str() happen to you

If you're coming to C++ from C, then you will quickly learn to love std::stringstream.  These things let you quickly build out a (possibly huge) string by just tacking on string literals or any other variables to the end.  It's useful for building on-the-fly SQL queries or constructing configuration or connection strings that involve numbers (such as port numbers), since you don't have to pre-define a buffer of known length and snprintf onto the end of it and check for length issues and such.

And you'll love std::string, since that'll save you countless "strdup" calls and null checks.  std::string also has some extra powers that make him way more useful than character buffer manipulation, but still less amazing (and heavy) than std::stringstream.

Anyway, you'll also quickly find that most functions don't accept std::stringstream or std::string; rather, they accept "const char*", which is fine by me.  In fact, std::string has a "c_str" function that will return just such a pointer, and std::stringstream has a "str" function that will return a std::string, so that's great, right?

Yes, absolutely.

But watch out!

But watch out for this:
//! This is our string stream; we're just going to put something
//! in it for fun.  This example will use a made-up connection string.
std::stringstream myStringStream;
// Set up the "connection string"; note for example purposes that
// these could be variables of any type; much like the thing at the
// end is an integer.
myStringStream << "tcp://" << "localhost" << ":" << 9001;

// Create a character pointer so that another function can use it.
const char* myPointer = myStringStream.str().c_str();

// Use that in some function.
someCStyleFunction( myPointer );

Did you see the problem?

When "myPointer" was created, it called "c_str" on a string that was only alive for the duration of that line.  After that line is over, the string that generated the character pointer has been deleted; thus, the pointer to its data is invalid.

Valgrind will complain about this as accessing some memory that was deleted by the destructor of std::string, but you'll probably be too confused to realize what's going on.

In a single-threaded situation, you might be able to slide by without noticing this because nothing has used that memory just yet.  However, in a multi-threaded situation, that memory is essentially instantly whisked up by other threads for other uses.  And now your character pointer points to random other data.  Welcome to what might be hours of troubleshooting and debugging.

The proper solution

Since "c_str" returns a pointer to the internal buffer of a std::string, and since you don't have to free it, it means that the character pointer that it returns is only valid for the lifetime of the std::string that it came from.

Our earlier example could be addressed in one of two ways.

The sneaky way

Don't let the std::string go out of scope by ending the line.  The "str" function's result, a std::string, won't be cleaned up until after "someCStyleFunction" completes, so this gets around the problem.  However, later expansion or debugging of the code might inadvertantly re-introduce it.  Avoid this method.
//! This is our string stream; we're just going to put something
//! in it for fun.  This example will use a made-up connection string.
std::stringstream myStringStream;
// Set up the "connection string"; note for example purposes that
// these could be variables of any type; much like the thing at the
// end is an integer.
myStringStream << "tcp://" << "localhost" << ":" << 9001;

someCStyleFunction( myStringStream.str().c_str() );

The classy way

Actually store the std::string so that it goes out of scope when you want it to.  This makes it clear what the string is for and what its scope is.
//! This is our string stream; we're just going to put something
//! in it for fun.  This example will use a made-up connection string.
std::stringstream myStringStream;
// Set up the "connection string"; note for example purposes that
// these could be variables of any type; much like the thing at the
// end is an integer.
myStringStream << "tcp://" << "localhost" << ":" << 9001;

//! This is the string that we have created with our string stream.
std::string myString = myStringStream.str();

// Create a character pointer so that another function can use it.
const char* myPointer = myString.c_str();

// Use that in some function.
someCStyleFunction( myPointer );

Hopefully this might save you some time.  I spent hours researching the thread-safety of the STL for my current g++ version and was lead down all kinds of wrong paths for a simple, simple scoping issue.

3 comments:

  1. Great article which saved time for me. Thanks.

    ReplyDelete
  2. And how then release those myPointer?

    ReplyDelete
    Replies
    1. You don't have to release it, it will be released automatically by the string object when the execution exits the scope.

      Delete