Tuesday, May 30, 2017

Avoiding heap allocation with strings


I was wanting to debug my memory allocations and hit a problem. The logging code that prints out useful information to help debug and trace the program is itself allocating memory. This is particularly pronounced with the string manipulations it does and with creating formatted strings from a variable list of arguments.

It got me thinking. Firstly when one has a std::string object, particularly if that object itself was on the stack, it still allocates off the heap, it is too bad there is no way to allocate off the stack as the lifetime of the object is on the stack anyway. This is just how it is in C/C++. You can sort of do variable sized allocations off the stack, such as alloca or a variable sized array, such as:

   size_t var = i * 10;
   char array[var];

Depending on the compiler, that can work. But anyway, this is no good for a general kind of string class as the lifetime of this is the scope it is created in so won't persist when the function returns. The same applies for alloca.

But I really want to solve this and completely avoid making heap allocations while logging. The main culprit is formatting a string from a format argument and a variable list of arguments.


So the first thing to do is work out the required size for this variable sized array to format in to.


void Logger(enum LogLevel a_level, const char* a_fmt, va_list a_args)
{
  va_list args2;
  va_copy(args2, a_args);
  size_t msgSiz = std::vsnprintf(nullptr, 0, a_fmt, a_args) + 1;
  char msg[msgSiz];
  ::vsnprintf(msg, msgSiz, a_fmt, args2);

  va_end(args2);
   ...
}


Now msg contains the formatted string and without using the heap. The only tricky thing is having to make a copy of the va_list because it gets consumed in the first call to vsnprintf and we need a fresh copy to pass to the 2nd call to it we make.

That wasn't too hard :)