RR: Avoiding Memory Allocation

The C++ language has a range of excellent tools for managing allocation and de-allocation of memory. They are an essential part of writing effective and efficient programs. The first strategy, however, for dealing with memory allocation should be to avoid it. If that is not possible, you should try to separate the logic of what you are trying to achieve from the memory allocation routines.

Example

As usual, it is easiest to criticise an example:

// Bojan Nikolic <bojan@bnikolic.co.uk>
//
// An examle of how not the generate random numbers

#include <boost/random.hpp>
#include <iostream>

double * getRand(size_t n)
{
  boost::mt19937 rng(43);
  static boost::uniform_01<boost::mt19937> zeroone(rng);

  double *res= new double[n];
  for(size_t i=0; i<n; ++i)
  {
    res[i]=zeroone();
  }
  return res;
}

int main(void)
{
  const size_t n=10;
  double *r=getRand(n);
  for(size_t i=0; i<n; ++i)
  {
    std::cout<<r[i]<<" ";
  }
}

What are the problems with the function getRand()?

  • It returns a pointer to newly allocated memory that the caller must know to de-allocate with delete[] (and the user has forgotten to do that in the example above)
  • It forces to user to allocate memory even if she wants only one random number
  • It provides no flexibility in how memory is allocated, e.g., maybe the user wants to use a memory pool or similar strategy

As you can see, all of the above are related to the basic issue that the function is trying to do two things at once:

  1. Generating random numbers
  2. Allocating memory for numbers

Like it is emphasised in basic programming courses, trying to do two things in one function is a bad idea and this is no exception.

Improved example

Here is an improved example:

// Bojan Nikolic <bojan@bnikolic.co.uk>
//
// Improved example

#include <boost/random.hpp>
#include <iostream>

void getRand(double *res,
          size_t n)
{
  boost::mt19937 rng(43);
  static boost::uniform_01<boost::mt19937> zeroone(rng);

  for(size_t i=0; i<n; ++i)
  {
    res[i]=zeroone();
  }
}

int main(void)
{
  const size_t n=10;
  double * r = new double[n];
  getRand(r,n);
  for(size_t i=0; i<n; ++i)
  {
    std::cout<<r[i]<<" ";
  }
}

As you can see the signature of the function has changed to:

void getRand(double *res,
             size_t n);

In other words, the user supplies the space and the functions concentrates only on generating the random number. Therefore it is possible to efficiently generate even single random numbers, to continuously reuse the same space, etc.

Next problem

We are still allocating memory using clunky double * r = new double[n]; and as eventually always happens with such allocations, forgetting to de-allocate. In next article, I will provide suggestions on how to avoid that.

Summary

Although I've used a concrete example above, there is a useful general tip to maybe take away:

If you can in advance determine the memory required by a function, allow the user an option of supplying that memory rather than allocating it within the function.

The advantage of this approach is that:

  • It can be faster
  • It avoids possible memory fragmentation
  • It separates code into smaller more manageable units
  • It reduces the probability of errors in the program