Well, I guess I wasn't very clear in separation. In the example, std::string IS the C++ std::string and the point was to show that in C++ resources such as memory is allocated and released for you if you use the constructors and destructors for that purpose. So copying B to C in C++ will release the memory in C, then copy over B into C. Thus preventing memory leaks.
Now, as I said, I have a struct called cstring in the C language. If I create/construct one of these cstring objects and just use the return value, then there is a chance for memory leaks.
Code: Select all
typedef struct
{
size_t length, capacity;
char* buffer;
}cstring;
cstring cs_default_construct()
{
cstring cs;
cs.length = 0;
cs.capacity = 1;
cs.buffer = (char*)malloc(1);
return cs;
}
cstring cs_string_construct( const char* str )
{
const size_t len = strlen( str );
cstring cs;
cs.length = len;
cs.capacity = len;
cs.buffer = (char*)malloc(len);
for(size_t i = 0; i < len; ++i)
{
cs.buffer[i] = str[i];
}
return cs;
}
cstring cs_copy(const cstring* _this)
{
cstring cs;
cs.length = _this->length;
cs.capacity = _this->capacity;
cs.buffer = (char*)malloc(_this->length);
for(size_t i = 0; i < _this->length; ++i)
{
cs.buffer[i] = _this->buffer[i];
}
return cs;
}
void cs_destroy(cstring* _this)
{
free( _this->buffer );
_this->buffer = NULL;
_this->length = 0;
_this->capacity = 0;
}
int main( int argc, char* argv[] )
{
cstring a = cs_string_construct("This is a line of text.");
cstring c = cs_string_construct("Here comes the pain.");
{
cstring b = a; // Problem here is b.buffer shares the same pointer as a.buffer
c = b; // Here, c.buffer is now pointing to b.buffer and the original c.buffer data is lost...memory leak.
c = cs_copy(b); // Still memory leaks, because the cs_copy function doesn't know about c so it can't release it's resources.
}
cs_destroy(a) // Should be fine
cs_destroy(c) // Will crash because c.buffer points to b.buffer and b.buffer was pointing to a.buffer and a.buffer has already been freed.
return 0;
}
This can still happen in C++ if you play with pointers, but if you use objects and their constructors, destructors and operator overloads of operator= you can avoid these types of memory leaks. This is why I say C is dangerous and if you are going to abstract the resource into a struct like this, you probably shouldn't return by value the resource wrapper like shown.
So I chose to do this instead:
Code: Select all
void cs_default_construct(cstring*const _this)
{
if(_this->length > 0)
{
cs_destroy(_this);
}
_this->length = 0;
_this->capacity = 1;
_this->buffer = (char*)malloc(1);
}
void cs_string_construct( cstring*const _this, const char* str )
{
if(_this->length > 0)
{
cs_destroy(_this);
}
const size_t len = strlen( str );
_this->length = len;
_this->capacity = len;
_this->buffer = (char*)malloc(len);
for(size_t i = 0; i < len; ++i)
{
_this->buffer[i] = str[i];
}
}
void cs_copy(const cstring* const _this, cstring* const other)
{
// Clean up resources if other contains something
if( other->length != 0 )
{
cs_destroy( other );
}
other->length = _this->length;
other->capacity = _this->capacity;
other->buffer = (char*)malloc(_this->length);
for(size_t i = 0; i < _this->length; ++i)
{
other->buffer[i] = _this->buffer[i];
}
}
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com