Free memory can be found in two forms:
- The free space between the heap and the stack. This is usually the
main chunk of free memory.
- Heap “holes”, i.e. pieces of unallocated memory within the heap. The
presence of these holes is called “heap fragmentation”.
There are various versions of the MemoryFree library. The simplest ones
only report the main chunk between the heap and the stack. The more
sophisticated ones walk the malloc()'s free list in order to also count
the heap holes. Which approach is better? I would argue that the naive
version is fine, as heap holes is something you should not count on,
because it is never guaranteed that a particular allocation will fit in
any of those holes.
You are visibly using one of the naive versions. As you grow your
string, the object calls
realloc(), which in some cases copies the
string's character array and frees the original one, potentially
creating a heap hole. Eventually, small holes can coalesce into a
larger one big enough for the next allocation, and
realloc() can end up using this large hole and freeing the chunk at
the top of the heap. When this happens, the heap shrinks, and the main
chunk of free memory gets larger, which is what
reporting to you.
In summary, it's normal and expected behavior. If you want something
less surprising, use a version of the library that does count the holes.
But then you may run into a more displeasing surprise: a heap–stack
collision, or an out-of-memory condition, can happen even with loads of
memory buried inside heap holes.