Infamous to C and C++ are those annoying memory leaks. In Lisp a programmer does not have to worry about memory allocation but it is a different story in C++. When a program allocates memory and neglects to deallocate the memory when it is no longer needed then a potential memory leak exists. If a program has a memory leak in some code and then loops on that code then eventually the program will use up available memory and break with a memory allocation error.
In C++ one must either link with one of the many automatic garbage collector libraries or else each function must make sure which data it owns and manually deallocate the data's memory when it is no longer referenced. Lpp provides some manual garbage collection functions which help See Garbage Collection. But the programmer must still be aware of how potential memory leaks can occur.
All Lpp data is allocated from the heap and not the stack. So no Lpp data is automatically deallocated by virtue of exiting a function. Symbols and character objects though are not problematical. There is only one set of character objects that are shared by all programs. And symbols once interned are also shared by all programs. So for example in the following code
list1 = list(S(sym1), S(sym1), S(sym1))
sym1 will only get allocated on its first
internment. Or in other words, in the case of
list1 the first
S(sym1) will intern the symbol
sym1 and allocate it
(assuming it has not been interned previously) and then return the
symbol. The second and third
S(sym1) then will only return the
already interned symbol
Usually the programmer will never have a need to deallocate symbols
since the nature of their semantics is to be persistent within the
full extent of the program. Symbols can be deallocated with
unintern if it is absolutely necessary See Creating Symbols.
To be safe then, all other Lpp objects should be garbage collected when known to be no longer referenced or else simulate the Symbol semantics. For example if in a function, we reference a commonly used string like the following
let b = stringConcat(a, L("-something-common"));
and that function can be called repeated times, then the string should either be made static
static let common_string = L("-something-common"); let b = stringConcat(a, common_string);
or alternatively it can be manually garbage collected
let common_string = L("-something-common"); let b = stringConcat(a, common_string); gc(common_string);
otherwise there will be no reference to such strings allocated and when the function exits the string objects would exist in memory until the program exits.
The manual garbage collection in this last case is a bad choice since we know that the same string will be used over and over again. The static variable containing the common string would be a better choice. But in a case where there is an intermediate value whose value we can not predict then a manual garbage collection would be more appropriate. The following for example would be a such a memory leak
let a = minus(d, times(b, c));
where b, c and d are input variables. The memory leak would be where
times returns a value. That value is not referenced
anywhere after the minus is executed. So to prevent a memory leak
here this should be coded instead as
let temp = times(b, c); let a = minus(d, temp); gc(temp);
The kinds of potential memory leaks mentioned above are relatively
simple to find and fix. But in the real world where large complex
data structures are passed around, memory leak bugs can be quite
difficult to pin down. It is a classical problem of which function
owns what data when. Lpp provides some assist to this by making it
easier to sweep up large branches of data that are no longer
referenced with functions like
gcTree but the
programmer must carefully design the growth and collapse of such
complex structures so as to avoid memory leaks.