Memory leaks occur when new memory is allocated dynamically and never deallocated. In C programs, new memory is allocated by the malloc or calloc functions, and deallocated by the free function. In C++, new memory is usually allocated by the new operator and deallocated by the delete or the delete [] operator.
The problem with memory leaks is that they accumulate over time and may cripple or even crash a program, if left unchecked. One of the most common mistakes leading to memory leaks is applying the wrong delete operator. The delete operator should be used to free a single allocated class or data value, whereas the delete [] operator should be used to free an array of data values. In C programming, the free function does not make this difference.
Another common mistake is overwriting a variable containing dynamic memory without freeing any existing memory first. For example, assume that thestring is a data member of a class, and in one of the methods (other than the constructor), there is the following statement:
thestring = new char[buflen];
This code should be
delete [] thestring;
thestring = new char[buflen];
Using delete is not necessary in a class’ constructor because the data member would not have been allocated previously.
In the MLDesigner primitives, the cleanup method should contain code that deletes variables dynamically allocated. In the primitive’s constructor method, the variables containing dynamic memory should be initialized to zero. By freeing memory using the cleanup method, one covers all possible cases of memory leak during simulation. Deallocating memory in the setup method handles the situation where a simulation is restarted, whereas deallocating memory in the cleanup covers the case in which a simulation is ended before or after the wrapup method. This includes cases where error messages are generated and the simulation cannot be continued.
Another common mistake is not paying attention to the kinds of strings returned by functions. The function savestring returns a new string dynamically allocated and should be deleted when no longer used. The expandPathName, tempFileName and makeLower functions return new strings, like the Target::writeFileName method. Therefore, the strings returned by these routines should be deleted when they are no longer needed, and code such as
savestring( expandPathName(s) )
is redundant and should be simplified to
expandPathName(s)
to avoid a memory leak due to not keeping track of the dynamic memory returned by the function expandPathName.
Occasionally, dynamic memory is being used where local memory would be more convenient. For example, if a variable is only used as a local variable inside a method or function and the value of the local variable is not returned or passed to outside the method or function, then it would be better to simply use local memory. For example,
char* localstring = new char[len + 1];
if ( person == absent ) return;
strcpy(localstring, otherstring);
delete [] localstring;
return;
could easily return without deallocating localstring. The code should be rewritten to use either the StringList or InfString class, e.g.,
InfString localstring;
if ( person == absent ) return;
localstring = otherstring;
return;
Both StringList and InfString can manage the construction of strings of arbitrary size. When a function or method finishes its execution, the destructors of the StringList and InfString variables will automatically be called and will deallocate their memory. Casts have been defined that can convert StringList to a const char* string and InfString to a const char* or a char* string, so that instances of the StringList and InfString classes can be passed as they are into routines that take character array (string) arguments. A simple example of using the StringList class is a function which builds up an error message into a single string:
StringList sl = msg;
sl << file << ": " << sys_errlist[errno];
ErrAdd(sl);
The ErrAdd function takes a const char* argument, so sl will converted automatically to a const char* string by the C++ compiler.
Instead of using the new and delete operators, it is tempting to use constructs like
char localstring[buflen + 1];
in which buflen is a variable, because the compiler will automatically handle the deallocation of the memory. Unfortunately, this syntax is a GNU g++ extension and not portable to other C++ compilers. Instead, the StringList and InfString classes should be used, as the previous example involving localstring illustrates.
Sometimes the return value from a routine that returns dynamic memory is not stored, and therefore, the pointer to the dynamic memory gets lost. This occurs, for example, in nested function calls. Code like
puts( savestring(s) );
should be written as
const char* newstring = savestring(s);
puts( newstring );
delete [] newstring;
Several places in MLDesigner, especially in the schedulers and targets, rely on the hashstring function, which returns dynamic memory. This dynamic memory, however, should not be deallocated because it may be reused by other calls to hashstring. It is the responsibility of the hashstring function to deallocate any memory it has allocated.