cnumerical-recipes

Question on book Numerical recipes, 2nd ed.: allocation/deallocation of memory for vectors


The book Numerical recipes, 2nd edition (http://numerical.recipes) uses the following code to allocate/deallocate a memory for a vector v with subscripts [nl..nh]:

#define NR_END 1
#define FREE_ARG char*

float *vector(long nl, long nh)
/* allocate a float vector with subscript range v[nl..nh] */
{
  float *v;

  v=(float *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(float)));
  if (!v) nrerror("allocation failure in vector()");
  return v-nl+NR_END;
}

void free_vector(float *v, long nl, long nh)
/* free a float vector allocated with vector() */
{
  free((FREE_ARG) (v+nl-NR_END));
}

Question 1: What is the purpose of adding/subtracting NR_END elements?

Question 2: What is the purpose of converting float * to char * in free_vector?

I understand that +1 in malloc is due to the inclusive right boundary of the array (which is non-inclusive usually in C).


Solution

    1. Suppose you had nl=1 and NR_END=0. Then the returned pointer would be out of bounds (it points before the allocated block). This is undefined behavior and can lead to incorrect results, although it is unlikely to cause problems on major compilers because the pointer would be incremented back before it is dereferenced.

      To avoid this undefined behavior, you can set NR_END to the maximum expected value of nl (which is 1 in the book). This guarantees that the returned pointer is valid. However, the implementation given in the question is still incorrect, because v-nl+NR_END decrements by nl before incrementing by NR_END. A correct implementation would be v+NR_END-nl.

      Note that if nl only ever has non-negative values, a much simpler implementation would be to simply allocate nh+1 values, and then you don't need any pointer arithmetic after malloc or before free.

      Here you can see a quote from the book explaining this, from pages 940-941 of the second edition. Some quotes:

      it might happen in rare cases (and probably only on a segmented machine) that the expression b-1 has no representation at all. If this occurs, then there is no guarantee that the relation b=(b-1)+1 is satisfied.

      [....]

      the parameter NR_END is used as a number of extra storage locations allocated at the beginning of every vector or matrix block, simply for the purpose of making offset pointer references guaranteed-representable.

    2. The cast to char* is not needed in any standardized version of C. It may have been needed in ancient versions. Casting the return value of malloc is also not needed.