Allocate contiguous memory Allocate contiguous memory unix unix

Allocate contiguous memory


Remember that memory is just memory. Sounds trite, but so many people seem to think of memory allocation and memory management in C as being some magic-voodoo. It isn't. At the end of the day you allocate whatever memory you need, and free it when you're done.

So start with the most basic question: If you had a need for 'n' double values, how would you allocate them?

double *d1d = calloc(n, sizeof(double));// ... use d1d like an array (d1d[0] = 100.00, etc. ...free(d1d);

Simple enough. Next question, in two parts, where the first part has nothing to do with memory allocation (yet):

  1. How many double values are in a 2D array that is m*n in size?
  2. How can we allocate enough memory to hold them all.

Answers:

  1. There are m*n doubles in a m*n 2D-matrix of doubles
  2. Allocate enough memory to hold (m*n) doubles.

Seems simple enough:

size_t m=10;size_t n=20;double *d2d = calloc(m*n, sizeof(double));

But how do we access the actual elements? A little math is in order. Knowing m and n, you can simple do this

size_t i = 3; // value you want in the major index (0..(m-1)).size_t j = 4; // value you want in the minor index (0..(n-1)).d2d[i*n+j] = 100.0;

Is there a simpler way to do this? In standard C, yes; in C++ no. Standard C supports a very handy capability that generates the proper code to declare dynamically-sized indexible arrays:

size_t m=10;size_t n=20;double (*d2d)[n] = calloc(m, sizeof(*d2d));

Can't stress this enough: Standard C supports this, C++ does NOT. If you're using C++ you may want to write an object class to do this all for you anyway, so it won't be mentioned beyond that.

So what does the above actual do ? Well first, it should be obvious we are still allocating the same amount of memory we were allocating before. That is, m*n elements, each sizeof(double) large. But you're probably asking yourself,"What is with that variable declaration?" That needs a little explaining.

There is a clear and present difference between this:

double *ptrs[n];  // declares an array of `n` pointers to doubles.

and this:

double (*ptr)[n]; // declares a pointer to an array of `n` doubles.

The compiler is now aware of how wide each row is (n doubles in each row), so we can now reference elements in the array using two indexes:

size_t m=10;size_t n=20;double (*d2d)[n] = calloc(m, sizeof(*d2d));d2d[2][5] = 100.0; // does the 2*n+5 math for you.free(d2d);

Can we extend this to 3D? Of course, the math starts looking a little weird, but it is still just offset calculations into a big'ol'block'o'ram. First the "do-your-own-math" way, indexing with [i,j,k]:

size_t l=10;size_t m=20;size_t n=30;double *d3d = calloc(l*m*n, sizeof(double));size_t i=3;size_t j=4;size_t k=5;d3d[i*m*n + j*m + k] = 100.0;free(d3d);

You need to stare at the math in that for a minute to really gel on how it computes where the double value in that big block of ram actually is. Using the above dimensions and desired indexes, the "raw" index is:

i*m*n = 3*20*30 = 1800j*m   = 4*20    =   80k     = 5       =    5======================i*m*n+j*m+k     = 1885

So we're hitting the 1885'th element in that big linear block. Lets do another. what about [0,1,2]?

i*m*n = 0*20*30 =    0j*m   = 1*20    =   20k     = 2       =    2======================i*m*n+j*m+k     =   22

I.e. the 22nd element in the linear array.

It should be obvious by now that so long as you stay within the self-prescribed bounds of your array, i:[0..(l-1)], j:[0..(m-1)], and k:[0..(n-1)] any valid index trio will locate a unique value in the linear array that no other valid trio will also locate.

Finally, we use the same array pointer declaration like we did before with a 2D array, but extend it to 3D:

size_t l=10;size_t m=20;size_t n=30;double (*d3d)[m][n] = calloc(l, sizeof(*d3d));d3d[3][4][5] = 100.0;free(d3d);

Again, all this really does is the same math we were doing before by hand, but letting the compiler do it for us.

I realize is may be a bit much to wrap your head around, but it is important. If it is paramount you have contiguous memory matrices (like feeding a matrix to a graphics rendering library like OpenGL, etc), you can do it relatively painlessly using the above techniques.

Finally, you might wonder why would anyone do the whole pointer arrays to pointer arrays to pointer arrays to values thing in the first place if you can do it like this? A lot of reasons. Suppose you're replacing rows. swapping a pointer is easy; copying an entire row? expensive. Supposed you're replacing an entire table-dimension (m*n) in your 3D array (l*n*m), even more-so, swapping a pointer: easy; copying an entire m*n table? expensive. And the not-so-obvious answer. What if the rows widths need to be independent from row to row (i.e. row0 can be 5 elements, row1 can be 6 elements). A fixed l*m*n allocation simply doesn't work then.

Best of luck.


Never mind, I figured it out.

  /* The total number of bytes allocated was:  8    0x7fb35ac038c0 - 1     0x7fb35ac038c8 - 2     0x7fb35ac038d0 - 3     0x7fb35ac038d8 - 4     0x7fb35ac038e0 - 5     0x7fb35ac038e8 - 6     0x7fb35ac038f0 - 7     0x7fb35ac038f8 - 8 */double ***d3darr(size_t l, size_t m, size_t n);int main(int argc, char const *argv[]){    int m,n,l,i;    double *** f;    m = n = l = 10; i = 0;    f = d3darr(sizeof(l), sizeof(m), sizeof(n));    printf("%s %d\n", "The total number of bytes allocated was: ", m * n * l);    for (i=0;i<n*m*l;++i) {        printf("%p - %d\n ", &f[i], i + 1);    }    return 0;}double ***d3darr(size_t l, size_t m, size_t n){    double *** ptr1 = (double ***)malloc(sizeof(double **) * m * n * l);    double ** ptr2 = (double **)malloc(sizeof(double *) * m * n);    double * ptr3 = (double *)malloc(sizeof(double) * m);    int i, j;    for (i = 0; i < l; ++i) {        ptr1[i] = ptr2+m*n*i;        for (j = 0; j < l; ++j){            ptr2[i] = ptr3+j*n;        }    }    return ptr1;}