2

As an introduction, note that I am a Java programmer still getting used to the memory management issues in C++.

We have a base class which is used to encoded objects to a string of ASCII characters. Essentially, the class is using a stringstream class member to convert different datatypes to one long string, and then returns a char* to the caller which contains the encoded object data.

In testing for memory leaks, I am seeing that the implementation we are using seems prone to create memory leaks, because the user has to always remember to delete the return value of the method. Below is an excerpt of the relevant parts of the code:

    char* Msg::encode() 
    {
        // clear any data from the stringstream
        clear();
        if (!onEncode()) {
            return 0;
        }

        // need to convert stringstream to char*
        string encoded = data.str();
                    // need to copy the stringstream to a new char* because 
                    // stringstream.str() goes out of scope when method ends
        char* encoded_copy = copy(encoded);
        return encoded_copy;
    }

    bool Msg::onEncode(void)
    {
        encodeNameValue(TAG(MsgTags::TAG_USERID), companyName);
        encodeNameValue(TAG(MsgTags::TAG_DATE), date);
        return true;
    }

    bool EZXMsg::encodeNameValue(string& name, int value)
    {
        if(empty(value))
        {
            return true;
        }
                    // data is stringstream object
        data << name << TAG_VALUE_SEPARATOR << value << TAG_VALUE_PAIRS_DELIMITER;
        return true;
    }


    char* copy(string& source) {
        char *a=new char[source.length() +1];
        a[source.length()]=0;
        memcpy(a,source.c_str(),source.length());
        return a;
    }

UPDATE

Well - I should have been more accurate about how the result of encode() is consumed. It is passed to boost:async_write, and program is crashing because I believe the string goes out of scope before async_write complete. It seems like I need to copy the returned string to a class member which is alive for life time of the class which sends the message (?).

This is the way the encode() method is actually used (after I changed the return value of to string):

void iserver_client::send(ezx::iserver::EZXMsg& msg) {
       string encoded = msg.encode();   
       size_t bytes = encoded.length();
       boost::asio::async_write(socket_, boost::asio::buffer(encoded, bytes), boost::bind(&iserver_client::handle_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));    
}

It looks like the proper way to do this is to maintain a queue/list/vector of the strings to async write. As noted here (and also in the boost chat_client sample). (But that is a separate issue.)

Community
  • 1
  • 1
Sam Goldberg
  • 6,711
  • 8
  • 52
  • 85
  • 1
    It depends on what the desired API is. The big question -- how long is the the returned `char *` supposed to be valid and who is supposed to free the memory associated with it? – David Schwartz Jul 14 '13 at 02:49
  • 5
    Why not just return a `string` from the `encode()` method (or alternatively pass in a reference to a `string`)? – Jonathan Potter Jul 14 '13 at 02:55
  • 1
    Don't use `char *` as the return type; in fact, avoid raw `char *` strings and use `std::string`. Use RAII (Resource Acquisition Is Initialization) to ensure that exceptions and normal use avoid leaks. Avoid `new` when you can; be very careful when you can't. – Jonathan Leffler Jul 14 '13 at 02:55
  • @DavidSchwartz: The char* doesn't need to be alive very long, just enough to send through a socket. Once it's been sent through the socket it can be deleted. It's using boost tcp::socket (async write), so assuming that boost is copying the char* then it just needs to stay in scope long enough to be posted into the async_write method. – Sam Goldberg Jul 14 '13 at 02:57
  • 1
    @SamGoldberg: I am not sure you can assume that boost is copying the buffer for you. I would be more inclined to believe the only copy being made is from user space to kernel space during the system call. So, the pointer probably cannot be freed until you get your asynchronous notification that the send has completed. – jxh Jul 14 '13 at 03:02
  • @JonathanPotter: If I understand your point - if I return string, then it will return by value, and will make a copy of the string when returning. which means the string is now on the stack, and gets cleaned up when it goes out of scope? – Sam Goldberg Jul 14 '13 at 03:02
  • @SamGoldberg, Technically, yes, though the actual string of characters is probably on the heap, but return value optimization and move semantics can avoid that copy. – chris Jul 14 '13 at 03:06
  • 1
    @Sam: A `string` object is just a wrapper around a `char*`. When you return, the returned object is a temporary, so the move constructor will be used, and this just gives the memory to the other `string` object instead of copying it. – Ben Voigt Jul 14 '13 at 03:11
  • @jxh: you are actually correct- and program is crashing because the string goes out of scope before async_write completes. – Sam Goldberg Jul 15 '13 at 01:41

4 Answers4

2

For this question: in your copy function you return a pointer to a heap memory!So user maybe create memory leak,I think you can not use this copy function,you can do just like this in your encode func:

return data.str();

If you want to get a char*, you can use the member function of string:c_str(), just like this:

string ss("hello world");
const char *p = ss.c_str();

If you use a stack string object you will not create memory leak,

minicaptain
  • 1,196
  • 9
  • 16
1

You could just return a std::string. You have one there anyway:

string Msg::encode() 
{
    // clear any data from the stringstream
    clear();
    if (!onEncode()) {
        return string{};
    }

    return data.str();
}

Then the caller would look like:

Msg msg;
msg.userID = 1234;
send(msg.encode().c_str());
Mankarse
  • 39,818
  • 11
  • 97
  • 141
1

The only way of achieving "automatic" deletion is with a stack variable (at some level) going out of scope. In fact, this is in general the only way of guaranteeing deletion even in case of an exception, for example.

As others mentioned std::string works just fine, since the char * is owned by the stack-allocated string, which will delete the char *.

This will not work in general, for example with non char * types.

RAII (Resource Acquisition is Initialization) is a useful idiom for dealing with such issues as memory management, lock acquisition/release, etc.

A good solution would be to use Boost's scoped_array as follows:

{
  Msg msg;
  msg.userID = 1234;
  scoped_array<char> encoded(msg.encode());
  send(encoded.get());
  // delete[] automatically called on char *
}

scoped_ptr works similarly for non-array types.

FYI: You should have used delete[] encoded to match new char[source.length() +1]

WaelJ
  • 2,942
  • 4
  • 22
  • 28
1

While using a std::string works adequately for your specific problem, the general solution is to return a std::unique_ptr instead of a raw pointer.

std::unique_ptr<char[]> Msg::encode() {
    :
    return std::unique_ptr<char[]>(encoded_copy);
}

The user will then get a new unique_ptr when they call it:

auto encoded = msg.encode();
send(encoded.get());

and the memory will be freed automatically when encoded goes out of scope and is destroyed.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • What is difference between using unique_ptr and the boost::shared_ptr? – Sam Goldberg Jul 15 '13 at 01:21
  • 1
    @SamGoldberg: a `unique_ptr` can't be copied, only moved, so its the only pointer to the object. A `std::shared_ptr` (pretty much the same thing as a `boost::shared_ptr`) has some overhead (a reference count) compared to a `unique_ptr` and does not work well with arrays, while `unique_ptr` has a specialized version for arrays. – Chris Dodd Jul 15 '13 at 21:57
  • Thanks for the explanation. It's all starting to make more sense. – Sam Goldberg Jul 17 '13 at 20:20