What’s wrong with allocating memory with new and then not erasing?

Asked

Viewed 337 times

5

What can happen if I use the new to allocate memory and then don’t release it?

And when it is in the situation below, that I can not erase the allocated memory, because I need to return, what I do?

const char* enc(const char* Str)
{
    auto len = strlen(Str);
    char* _Str = new char[len];

    for (size_t i = 0; i < len; i++) _Str[i] = Str[i] + 4;

    _Str[len] = '\0';
    return _Str;
}
  • Off-topic: half pedantic, but identifiers similar to _Aaa are reserved for the standard library and use this style in your code has undefined behavior.

3 answers

5

The "problem" is that each time the function is called, more memory is used. The problem is actually bigger if your program will work as a server or other type of long-term process, and run for hours/days - and at each function call will use a little more memory.

If it’s a quick, terminal interaction program that does something and ends, no problem - except for the violation of good programming practices.

Now, the interesting thing is this: actually in systems even of medium complexity, the normal is functions that "create" objects that will be used at other points in the program do not displace that memory. It is the responsibility of the code that called the function that creates an object allocating memory to destroy the created object. What to do in each case must be in the documentation of its function.

In this case, you just create a string - so just document that whoever calls this function should do a delete after using the return value - c++ will actually destroy the string itself if it goes out of scope in the code it was called from - and this happens to strings or other objects. If the code were pure C, whoever used its function would have to be instructed or call a function to destroy the object explicitly (necessary for structs containing pointers to other structs, for example), or call the function directly free. (In C++, the command delete or the exit of the scope call the destructor of the object - a special method that makes this action).

  • Even documenting this, many people do not read, or may forget to release this memory, I do not think that would be a good solution to the problem. Thanks for the tip and solution.

  • As you can see, in C++ is not a problem. The documentation is the correct way yes - because in C and C++ you have to know what to do with any object you’re dealing with in your code. If you received a valid string, you have to look if it was created with new or with malloc - is part of programming in these languages. (in general a well behaved C++ code base will always deliver objects created with new erased - but who uses the functions has what to do)

3


The role of the operator new is to allocate a memory region (heap, not the execution stack) to the data and invoke in each cell the constructor of that data type. This means that each time the operator is executed new occupies more memory and, as memory has limit, can not call new the will without using delete before missing memory for another new.

Programs that need to make many memory allocations (such as symbolic math programs, web servers, and others) need to manage memory well, so they eliminate unnecessary data in case that memory region is needed for something later. Common programs that need to allocate a lot of data may also have problems like this, but it is less common.

Typically, in C++ the data allocated in a scope is out of focus on it, which is a good practice of programming based on variable life time control and ensures the misalignment of data no longer needed. In cases of data that need to persist in the above scopes, the data is already constructed in the variables of the original scope or copied to them.

To facilitate this control of life time, it is customary to use constructor of structures and objects of classes for the purpose of allocating the data whenever the objects are created and also the destructor to delete itlos, virtually automating the lifetime control of the data allocated in the heap without needing to remember to use new and delete at the right time.

With this, you could make a structure like this.

struct StringType {
private:
    char *strChars ;
    size_t strLen ;
    void constroy( size_t len ){
        strChars = new char[ (strLen=len)+1 ] ;
    }
    void constroyCopy( const StringType &string ){
        constroy( string.StrLen ) ;
        for( size_t index=0 ; index<=StrLen ; index++ ){
            StrChars[index] = string.StrChars[index] ;
        }
    }
    void destroy( void ){
        delete strChars ;
    }
public:
    ~StringType( void ){
        destroy() ;
    }
    StringType( size_t maxLength ){
        constroy(maxLength) ;
    }
    StringType( const StringType &string ){
        constroyCopy(string) ;
    }
    resetString( size_t newMaxLength ){
        destroy() ;
        constroy(newMaxLength ) ;
    }
    resetString( const StringType &string ){
        destroy() ;
        constroyCopy(string) ;
    }
    size_t length( void ){
        return strLen ;
    }
    const char* charPointer( void ){
        return strChars ;
    }
}

And then if you want the data to persist per copy you make a function that returns the structure, then it will be copied.

StringType char* enc( const char* Str ){
    StringType _Str( strlen(Str) ) ;
    for( size_t i=0 ; i<_Str.length() ; i++ ) _Str.charPointer[i] = Str[i]+4 ;
    _Str.charPointer[len] = '\0';
    return _Str ;
}

But if you prefer a better performance you can save the data directly into a destination structure by passing your address as argument.

void enc( const char* Str , String *Str_out ){
    Str_out.resetString( strlen(Str) ) ;
    for( size_t i=0 ; i<Str_out.length() ; i++ ) Str_out.charPointer[i] = Str[i]+4 ;
    Str_out.charPointer[len] = '\0';
}

Note: in this section of your code

auto len = strlen(Str);
char* _Str = new char[len];

you allocated index cells 0, 1, ..., len-1 and in this

_Str[len] = '\0';

you saved in index cell len, that doesn’t exist. To avoid possible errors (such as having something else allocated in that region of memory that will be lost), allocate one more cell. In the codes I’ve made, I’ve already automated it by allocating (strLen=len)+1 cells in the construction of the string-type structure.

Any doubt?

Edit: there is an alternative solution that preserves its way of programming, does not require an implemented structure and should perform better. All you have to do is add a parameter that is a target character array so that you write the solution to it instead of allocating and returning an array of characters. With this, you have full control of data creation and removal outside the function, and may involve scopes or other criteria that you consider most appropriate.

void enc( const char* Str , char* _Str_out ){
    size_t len = strlen(Str) ;
    for( size_t i=0 ; i<len ; i++ ) _Str_out[i] = Str[i]+4 ;
    _Str_out[len] = '\0';
}

One more thing, you can do this without having to call the function that counts the number of characters in the string, thus improving the performance more.

void enc( const char* Str , char* _Str_out ){
    size_t i ;
    for( i=0 ; Str[i]!='\0' ; i++ ) _Str_out[i] = Str[i]+4 ;
    _Str_out[i] = '\0' ;
}
  • Another alternative I discovered would be to use an array within the function as Static and with a large array size to avoid overflows, like 4096 for example and return it. Thank you for your reply.

  • There is also this one, which is very efficient but does not give control over the size of the array. This we usually call buffer and should take care not to use too many buffers throughout the program and over fatten the executable in memory, leaving it heavy running.

0

Whereas you used to auto then you should be using at least C++11 then you might consider returning a std::unique_ptr which ensures that there is only one copy of the pointer. Thus, the user of your function does not need to worry about this because deletion is done automatically when the pointer goes out of scope.

A little off topic, also considering the use of C++11 (or higher) you should avoid to the maximum the use of "raw pointers".

  • I think for a simple function like this, it’s not necessary to use "smart pointers".

  • @cYeR maybe not for this function (although it is a way to break with the part of C++ that gives problem) but it is important to know that there is more this alternative.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.