Search code examples
cpointersdynamic-memory-allocation

Memory allocation in C from 1 to N


I have a function which allocates and initialize a 2D matrix. The code is as follows:

#include <stdio.h>
#include <stdlib.h>

#define SHIFT_IND 1

float **matrix(int mrl, int mrh, int mcl, int mch)
{

    int k,
        l,
        mrow = mrh - mrl + 1,
        mcol = mch - mcl + 1;
    float **ma;

    ma = (float **)malloc((size_t)((mrow + SHIFT_IND) * sizeof(float*)));
    if (ma == NULL)
        fprintf(stderr, "util.c: allocation failure 1 in function matrix()");
    ma += SHIFT_IND;
    ma -= mrl;

    ma[mrl] = (float *) malloc((size_t)((mrow * mcol + SHIFT_IND) * sizeof(float)));
    if (ma[mrl] == NULL)
        fprintf(stderr, "util.c: allocation failure 2 in function matrix()");
    ma[mrl] += SHIFT_IND;
    ma[mrl] -= mcl;

    for (k = mrl + 1; k <= mrh; k++)
        ma[k] = ma[k - 1] + mcol;

    for (k = mrl; k <= mrh; k++)
        for (l = mcl; l <= mch; l++)
            ma[k][l] = 0.0;

    return ma;
}

I guess this code makes the indices of a matrix from 1 to N (N is the total number of elements in each dimension),not from 0 to N-1 as tradition, and the size is still N*N. But I can't understand the key thought in this code, especially ma += SHIFT_IND; ma -= mrl;and ma[mrl] += SHIFT_IND; ma[mrl] -= mcl; Can anybody draw me a picture to illustrate how the memory is allocated from this code?


Solution

  • I can't understand the key thought in this code, especially ma += SHIFT_IND; ma -= mrl; and ma[mrl] += SHIFT_IND; ma[mrl] -= mcl;

    Consider the value of ma immediately after allocation, and suppose mrl is 3 and mrh is 6. The code will then have allocated ma with enough space for mrh - mrl + 1 + SHIFT_IND (== 5) elements. We can access those elements, among other ways, via expressions ma[0], ma[1], ... ma[4].

    Let's take the expression ma[3] as an example. Pointer indexing is an alternative syntax for expressing a combination of pointer arithmetic and dereferencing, in this case *(ma + 3).

    Now suppose that instead of modifying ma, we compute a new value of the same type, as mb = ma + SHIFT_IND - mrl, which for this example works out to mb = ma - 2. (We will return to this point a bit later.) Suppose further that we want to access the same element as ma[3], but through mb instead. A very little algebra gets us that ma + 3 == mb + 5, so if ma[3] means the same thing as *(ma + 3), and ma + 3 == mb + 5, then we can access the same storage location as mb[5]. By the same logic, we can also access ma[1] as mb[3], ma[2] as mb[4], and ma[4] (the last allocated element) as mb[6].

    That covers the range from mrl to mrh, though it does seem to waste one element, as nothing in that range accesses ma[0]. Except for the single-element lossage (which could be intentional) the idea is almost surely to produce a pointer that supports mrl-based indexing instead of 0-based indexing. And since ma is a float**, with all its elements themselves pointing to allocated spaces, the similar manipulations on the elements are analogous.

    Now returning as promised to the earlier point: the problem with the above scheme is that the computation mb = ma + SHIFT_IND - mrl (and ma += SHIFT_IND; ma -= mrl) has undefined behavior when the value of mrl exceeds 1, or when the value of mrl is sufficiently negative. In practice, it might nevertheless work as described above for some C implementations, but it probably fails in some others, and it might have inconsistent behavior in some. Its behavior is at relatively high risk to change with different levels of compiler optimization, or with code modifications. It is unsafe.

    Honestly, these indexing games look like a C adaptation of a Fortran idiom, because although Fortran defaults to 1-based array indexing, it permits arrays to be declared with any index range desired, including with negative bounds and with lower bounds greater than 1. C does not. In C, it would be much better to manipulate the indices with which the pointer is accessed than to manipulate the pointer value itself to (maybe) allow access with different indices.