/* Listing 2. */

/***************************************************
 *
 * Module Name:
 *    daa - dynamic array allocator
 *
 * Description:
 *    this dynamic array allocator is designed to be 
 *    efficient, i.e., it uses only one valloc() for 
 *    the entire array, and flexible. It can allocate
 *    arrays of up to 10 dimensions of any type that
 *    will return a size with the sizeof "C" operator.
 *    it is also very efficient from the point of 
 *    view of array access.  the single valloc() 
 *    ensures locality of reference that eliminates
 *    excess paging encountered with dynamic array 
 *    allocation schemes that use multiple malloc()'s.
 *    Unless the array is larger than a full page 
 *    it will be contained entirely on one page.  
 *    It can allocate arrays of structure, enum or 
 *    union type. A corresponding free(free(ptr)) 
 *    of the allocated area must normally be done 
 *    (unless you want to allocate to program ter-
 *    mination). The sixth parameter returns a 
 *    pointer to do a free on.  do not free on the
 *    function return pointer since arrays of non-
 *    zero subscripted first dimensions will not
 *    point to the allocated space, but some offset 
 *    based on the subscript offset of the first 
 *    dimension. The array is initialized to the 
 *    value pointed to by the last parameter, or 
 *    not initialized if  NULL.  the last parameter 
 *    pointer should point to something with a size
 *    the same as the size of the type in the sizeof
 *    first argument. arrays may be allocated to 
 *    have one or more dimensions with non-zero
 *    integer(+ or -) start subscripts.  thus, 
 *    arrays may be one based or zero based or 
 *    any based for that matter.
 *
 * parameters:
 *    size        - size of the basic array data 
 *                  object, this will usually be
 *                  passed from the sizeof() operator
 *                  in the call
 *
 *    no_dim      - number of array dimensions
 *
 *    dimensions  - a single dimensional array of 
 *                  the dimensions of the array to 
 *                  be allocated
 *
 *    start       - a single dimensional array of 
 *                  integer start subscripts for each 
 *                  corresponding dimension of the
 *                  dimensions array, may be negative
 *
 *    err_code    - pointer to returned error code 
 *                  value
 *
 *    free_ptr    - pointer to the base pointer of 
 *                  the vallocated array space  
 *                  for the caller to free
 *
 *    init_ptr    - initialization pointer parameter
 *                  (NULL if no initialization is 
 *                  desired).
 *
 * returns:
 *    a pointer to char that points to the start of 
 *    the dynamically allocated array area.  this 
 *    will normally need to be cast to the type of 
 *    the subsequent array references.  routine
 *    failure will return NULL.
 *
 * examples:
 *        char *fptr;
 *
 *    allocate 3 dimensional 3x3x3 zeroed int array 
 *    all zero based:
 *        int ***array;
 *        int init = 0;
 *        unsigned d[3] = {3,3,3};
 *        int st[3] = {0,0,0};
 *        array = (int ***)
 *         daa(sizeof(int), 3, d, st, &err_code, 
 *              &fptr, &init);
 *        array[0][2][1] = 1; assign int
 *        free(fptr);
 *
 *    allocate 2 dimensional 2x3 double array 
 *      initialized to 1.0 and one based:
 *        double **array;
 *        double init = 1.0;
 *        unsigned d[2] = {2,3};
 *        int st[2] = {1,1};
 *        array = (double ***)
 *         daa(sizeof(double), 2, d, st, &err_code, 
 *               &fptr, &init);
 *        array[1][2] = 1.5; assign double
 *        free(fptr);
 *
 *    allocate 3 dimensional 4x3x2 array of structures 
 *    initialized to the s_init structure and the 
 *    first index zero based with the second index five
 *    based and the third index -2 based:
 *        struct s {
 *            int i;
 *            double d;
 *        };
 *        struct s s_init = {5, 2.5};
 *        struct s ***array;
 *        unsigned d[3] = {4,3,2};
 *        int st[3] = {0,5,-2};
 *        array = (struct s ***)
 *         daa(sizeof(struct s), 3, d, st, &err_code,
 *               &fptr, &s_init);
 *        array[1][5][-2].i = 1; assign first element
 *        array[3][6][-2].d = 1.5; assign 2nd element
 *        free(fptr);
 *
 * errors:
 *    error free operation returns 0 in *err_code.
 *    any of the following errors will result in a 
 *    NULL pointer return and *err_code set to the 
 *    error number.
 *    1. number of dimensions must be > 0 and <= 10
 *    2. size must be greater than 1
 *    3. no dimension may be zero
 *    4. memory must be successfully allocated
 *        (valloc() != NULL)
 *
 *    failure to index the subscripts in the manner 
 *    established by the starting index array will 
 *    of course result in run time errors.  this is
 *    not an array allocation error but an array 
 *    usage error, similar to any array access on a
 *    static "C" zero based array outside the normal 
 *     0...n-1 bounds.
 *
 ****************************************************
 */
#include <stdio.h>

#define MAX_DIM 10 /* max no. of array dimensions */

/* these variables are used in the recursive 
   pointer setup routines */
static unsigned long data_size; /* data unit size*/
static unsigned long num_dim; /* # of dimensions */
   /* dimensions of array */
static unsigned long dim[MAX_DIM]; 
   /* product of dimensions from 0 to
      index, dim[0]*dim[1]...dim[index] */
static unsigned long dp[MAX_DIM];
   /* start index of array dimensions */
static int st[MAX_DIM];
   /* size of pointer */
static unsigned long ptr_size = sizeof(char *); 
   /* points to base of allocated array */
static char  *base; 

char *daa(size, no_dim, dimensions, 
         start, err_code, free_ptr, init_ptr)
    unsigned size;
    unsigned no_dim;
    unsigned dimensions[];
    int start[];
    unsigned *err_code;
    char **free_ptr;
    char *init_ptr;
{
    /* offset in pointer words of first data byte */
    unsigned long    data_off;
    /* array of current dimension indices */
    unsigned long    dim_ind[MAX_DIM];
    unsigned long    i,j;
    char          *p_data, /* pointer to array data */
                  *p; /* tmp pointer */

    char             *al();
    unsigned long    doff();
    unsigned long    off();

    *err_code = 0;
    if (no_dim < 1 || no_dim > MAX_DIM){
        *err_code = 1;
        return NULL;
    }

    if (size < 1){
        *err_code = 2;
        return NULL;
    }

    num_dim = no_dim;

    /* set dim_ind to all zeros, the multiple invocations 
     * of the recursive array allocation routine al() 
     * will pass changed by 1 dim_ind arrays to each 
     * invocation of al().  set dim[] and dp[] from 
     * dimensions[] input array and st[] from start[] 
     * input array */
    dp[0] = dimensions[0];
    for (i = 0;i < num_dim;i++){
        dim_ind[i] = 0;
        if (dimensions[i] == 0){
            *err_code = 3;
            return NULL;
        }
        dim[i] = dimensions[i];
        if (i > 0){
            dp[i] = dp[i-1]*dimensions[i];
        }
        st[i] = start[i];
    }

    data_size = size;

    /* allocate enough memory for the data plus 
       all the array pointers */
    if ((base = (char *)
        valloc((data_off = off(num_dim-1))*ptr_size +
                     dp[num_dim-1]*data_size)) == NULL){
        *err_code = 4;
        return NULL;
    }

    /* return allocated space pointer that caller 
     * should use to free space */
    *free_ptr = base;

    /* if init_ptr is NULL skip initialization */
    if (init_ptr != NULL){
         /* set p_data to point to the start of 
            the data area */
        p_data = base + data_off*ptr_size;
    
         /* initialize the array */
        for (i = 0;i < dp[num_dim-1];i++){
            p = init_ptr;
            for (j = 0;j < data_size;j++){
                *p_data++ = *p++;
            }
        }
    }

    /* do array setup i.e. all the pointer stuff */
    return al(0, dim_ind);
}

    static char *
al(level, dim_ind)
    unsigned long level;
    unsigned long dim_ind[];
{
    char **ptrs;
    unsigned long i;
    if (level < num_dim - 1){
        ptrs = (char **) (base + off(level)*ptr_size +
            ((level == 0)?0:doff(level, dim_ind, 
              dp[level])*ptr_size));
        for (i = 0;i < dim[level];i++){
            dim_ind[level] = i;
            ptrs[i] = al(level+1, dim_ind) -
                st[level+1]*((level+1 == num_dim-1) ?
                  data_size:ptr_size);
        }
        if (level == 0){
            ptrs -= st[0];
        }
    } else {
        ptrs = (char **) (base + off(level)*ptr_size +
                doff(level, dim_ind, dp[level])
                *data_size - ((level == 0) ? 
                (st[0]*data_size):0));
    }
    return (char *) ptrs;
}

    static unsigned long
doff(level, dim_ind, dim_prod)
    unsigned long level;
    unsigned long dim_ind[];
    unsigned long dim_prod;
{
    if (level == 0){
        return 0;
    } else {
        return doff(level-1, dim_ind, dim_prod) +
              (dim_prod/dp[level-1])*dim_ind[level-1];
    }
}

    static unsigned long
off(level)
    unsigned long level;
{
    if (level == 0){
        return 0;
    } else {
        return dp[level-1] + off(level-1);
    }
}
