0

I know how to do a potentioal non-contiguous array in the following way:

int main () {
  int ***array = (int***)malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    // Assign to array[i], not *array[i] (that would dereference an uninitialized pointer)
    array[i] = (int**)malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = (int*)malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}

with the code above, the array[0][j] blocks can be not contiguous. To get contiguous, I feel that we need to malloc in this way

int* array = (int*)malloc(3*3*3*sizeof(int));
int** y = (int**)malloc(3*3*sizeof(int**));
int*** x = (int***)malloc(3*sizeof(int***));

  for(i = 0; i < 3; i++)
    {
      vals = vals + i*m*n;
      x[i] = &vals;
      for(j = 0; j < 3; j++)
    {
    x[i][j] = vals + j * n;
    }
    }

However, I got troulbe with address assignment. I am not a c programmer, can anyone correct my fault? Thanks in advance...

David
  • 1,646
  • 17
  • 22
  • 1
    malloc doesn't care WHAT you're going to use the memory for. it just needs to know how big of a block you want. calculate how much total ram your array will occupy, and ask malloc for that much. initializing can also be easy. e.g. `memset(array, 0, total_bytes_in_array)` – Marc B Sep 30 '15 at 19:05
  • I have added a solution, which allocates everything contiguously and afterwards establishes row-pointers recursively. The data as well as the row-pointers are cleaned up using a `multi_free` function. I have versions where data is aligned also and where `unique_ptr`'s are used – Jens Munk Sep 30 '15 at 19:14
  • Don't cast `malloc` in C. – crashmstr Sep 30 '15 at 19:15
  • Does it have to be a malloc-allocated array? Perhaps you can use a normal array: **int array[3][3][3];** – Thomas Padron-McCarthy Sep 30 '15 at 19:20
  • **There is no 3D array here!** A pointer is not an array (or vice versa). – too honest for this site Sep 30 '15 at 19:42

5 Answers5

1
int*** x = (int***)malloc(3*sizeof(int***));

should be

int*** x = (int***)malloc(3*sizeof(int**));

Now initialization can be :

  for(i = 0; i < 3; i++)
  {
    x[i] = y + 3*i;
    for(j = 0; j < 3; j++)
    {
      x[i][j] = array + i*3*3 + j*3;
    }
  }

So that x[0] points to the first element of y, x[1] to the fourth, etc. And x[0][0]=y[0] to the first of array, x[0][1]=y[1] to the fourth of array, etc.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • 1
    It is incorrect to cast the output of malloc in C. That is only a C++ requirement, but not C. – ryyker Sep 30 '15 at 19:20
  • Would you go with _bad form_? Either way, its not a good idea. In C, it can lead to wrong assumptions about the pointer, or object returned. – ryyker Sep 30 '15 at 19:27
  • 1
    OP said he is not a C programmer, I'm not sure it will be very helpful for him to explain what is wrong with casting. There is no wrong assumptions with casting just a potential problem if `stdlib.h` not included. It is a long debate either. – Jean-Baptiste Yunès Sep 30 '15 at 19:31
1

To allocate a contiguous 3D array, you only need to do the following (assumes all dimensions are known at compile time):

#define D0 ...
#define D1 ...
#define D2 ...
...
T (*arr)[D1][D2] = malloc( sizeof *arr * D0 ); // for any type T
...
arr[i][j][k] = some_value();
...

arr is declared as a pointer to a D1xD2 array. We then allocate enough space for D0 such arrays (sizeof *arr == sizeof (T [D1][D2])).

With this method, all of the memory for the array is allocated contiguously. Also, you only need one call to free to deallocate the whole thing.

If your dimensions are not known until runtime and you're using a C99 compiler or a C2011 compiler that supports variable-length arrays, you're still in luck:

size_t d0, d1, d2;
...
T (*arr)[d1][d2] = malloc( sizeof *arr * d0 );

The main issue is how to pass this as an argument to a function. Assuming that D1 and D2 are known at compile time, if you decide to pass it as

foo( arr, D0 );

then the prototype for foo will need to be

void foo( T (*aptr)[D1][D2], size_t n ) 
{
  ...
  aptr[i][j][k] = ...;
}

and it will only be useful for n x D1 x D2-sized arrays.

If you go the VLA route, you'll need to declare the dimensions and pass values for them as well:

void foo( size_t d0, size_t d1, size_t d2, T (*aptr)[d1][d2] ) // d1 and d2 *must* be 
                                                               // declared before aptr
{
  ...
  arr[i][j][k] = some_value();
}

void bar( void )
{
  size_t d0, d1, d2;
  ...
  T (*arr)[d1][d2] = malloc( sizeof *arr * d0 );
  ...
  foo( d0, d1, d2, arr );
  ...
}

If you don't have a compiler that supports VLAs, but you still want to allocate this memory contiguously, then you'll have to go the old-fashioned route - allocate it as a 1D array and compute your offsets manually:

T *arr = malloc( sizeof *arr * d0 * d1 * d2 );
...
arr[i * d0 * d1 + j * d1 + k] = some_value(); 
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • `T *arr = malloc( sizeof *arr * d0 * d1 * d2 );` nicely done! – chux - Reinstate Monica Sep 30 '15 at 20:42
  • @chux - I wish I could say it was being smart, but that would be a lie. I just got into the habit of putting the `sizeof` expression first, without thinking about overflow issues. It just scanned better to me. – John Bode Sep 30 '15 at 20:47
  • I used to put the `sizeof()` last for `malloc()` as to resemble `calloc(count, size)` signature, but realized later the advantage of using `sizeof()` first. This will rise in importance with wider machines that have `size_t` > 32 bits, yet keep `int` 32-bit. – chux - Reinstate Monica Sep 30 '15 at 20:50
0

I am using some pretty neat methods for allocating multi-dimensional arrays with row pointers. The functions multi_malloc and multi_free can be used for arrays with arbitrary dimensions and arbitrary types and they work on basically all platforms and from C and C++

You can allocate, e.g. a 3-dimensional array with row-pointers recursively. E.g. a 10x20x30 dimensional array

float*** data = (float***) multi_malloc(sizeof(float),3, 10,20,20);

access elements like

data[2][3][4] = 2.0;

and free everything like (data as well as row pointers)

multi_free(data,3);

The header, which I think should be part of C is

#pragma once

#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>

#if (defined(_MSC_VER) && defined(_WIN32))
// Note when used inside a namespace, the static is superfluous
# define STATIC_INLINE_BEGIN static inline //__forceinline
# define STATIC_INLINE_END
#elif (defined(__GNUC__))
# define STATIC_INLINE_BEGIN static inline
# if defined(__CYGWIN__)
#  define STATIC_INLINE_END
# else
#  define STATIC_INLINE_END __attribute__ ((always_inline))
# endif
#endif

STATIC_INLINE_BEGIN void* multi_malloc(size_t s, size_t d, ...) STATIC_INLINE_END;

STATIC_INLINE_BEGIN void multi_free(void *r, size_t d) STATIC_INLINE_END;

/** 
 * Allocate multi-dimensional array and establish row pointers
 * 
 * @param s size of each element
 * @param d number of dimension
 * 
 * @return 
 */
STATIC_INLINE_BEGIN void* multi_malloc(size_t s, size_t d, ...) {

  char* tree;

  va_list ap;             /* varargs list traverser */
  size_t max,             /* size of array to be declared */
    *q;                   /* pointer to dimension list */
  char **r,               /* pointer to beginning of the array of the
                           * pointers for a dimension */
    **s1, *t;             /* base pointer to beginning of first array */
  size_t i, j;            /* loop counters */
  size_t *d1;             /* dimension list */

  va_start(ap,d);
  d1 = (size_t *) malloc(d*sizeof(size_t));

  for(i=0;i<d;i++)
    d1[i] = va_arg(ap,size_t);

  r = &tree;
  q = d1;                 /* first dimension */

  if (d==1) {
    max = *q;
    free(d1);
    return malloc(max*d);
  }

  max = 1;
  for (i = 0; i < d - 1; i++, q++) {      /* for each of the dimensions
                                           * but the last */
    max *= (*q);
    r[0]=(char *)malloc(max * sizeof(char **));
    r = (char **) r[0];     /* step through to beginning of next
                             * dimension array */
  }
  max *= s * (size_t) (*q);        /* grab actual array memory */
  r[0] = (char *)malloc(max * sizeof(char));

  /*
   * r is now set to point to the beginning of each array so that we can
   * use it to scan down each array rather than having to go across and
   * then down
   */
  r = (char **) tree;     /* back to the beginning of list of arrays */
  q = d1;                 /* back to the first dimension */
  max = 1;
  for (i = 0; i < d - 2; i++, q++) {    /* we deal with the last
                                         * array of pointers later on */
    max *= (*q);                        /* number of elements in this dimension */
    for (j=1, s1=r+1, t=r[0]; j<max; j++) { /* scans down array for
                                             * first and subsequent
                                             * elements */

      /*  modify each of the pointers so that it points to
       * the correct position (sub-array) of the next
       * dimension array. s1 is the current position in the
       * current array. t is the current position in the
       * next array. t is incremented before s1 is, but it
       * starts off one behind. *(q+1) is the dimension of
       * the next array. */

      *s1 = (t += sizeof (char **) * *(q + 1));
      s1++;
    }
    r = (char **) r[0];     /* step through to begining of next
                             * dimension array */
  }
  max *= (*q);              /* max is total number of elements in the
                             * last pointer array */

  /* same as previous loop, but different size factor */
  for (j = 1, s1 = r + 1, t = r[0]; j < max; j++)
    *s1++ = (t += s * *(q + 1));

  va_end(ap);
  free(d1);

  return((void *)tree);              /* return base pointer */
}

/** 
 * Free multi-dimensional array and corresponding row pointers
 * 
 * @param r data
 * @param d number of dimensions
 */
STATIC_INLINE_BEGIN void multi_free(void *r, size_t d) {

  void **p;
  void *next=NULL;
  size_t i;

  for (p = (void **)r, i = 0; i < d; p = (void **) next,i++)
    if (p != NULL) {
      next = *p;
      free(p);
      p = NULL;
    }
}
Jens Munk
  • 4,627
  • 1
  • 25
  • 40
0

Although I think a normal array, created on the stack would be best:

int array[3][3][3];//can avoid a lot of free() calls later on

Here is a way to create a 3D array dynamically:
(I use calloc here instead of malloc as it creates initialized memory space)

int *** Create3D(int p, int c, int r) 
{
    int ***arr;
    int    x,y;

    arr = calloc(p, sizeof(arr)); //memory for int
    for(x = 0; x < p; x++)
    {
        arr[x] = calloc(c ,sizeof(arr)); //memory for pointers
        for(y = 0; y < c; y++)
        {
            arr[x][y] = calloc(r, sizeof(int));
        }
    }
    return arr;
}

Usage could be:

int ***array = Create3D(3,3,3);
for(i=0;i<3;i++)
        for(j=0;j<3;j++)
                for(k=0;k<3;k++)
                    array[i][j][k] = (i+1)*(j+1)*(k+1);

Note that the return of [c][m][re]alloc() is not cast in this example. Although not strictly forbidden in C, it is not recommended. (this is not the case in C++, where it is required)
Keep in mind, everything allocated, must be freed. Notice freeing is done in reverse order of allocating:

void free3D(int ***arr, int p, int c)
{
    int i,j;
    for(i=0;i<p;i++)
    {
        for(j=0;j<c;j++)
        {
            if(arr[i][j]) free(arr[i][j]);
        }
        if(arr[i]) free(arr[i]);
    }
    if(arr) free(arr);
}

Usage could be:

free3D(array,3,3);
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • @chux - Ummm - yeah, it was a mess. Thanks. I re-worked it. – ryyker Oct 01 '15 at 13:20
  • `calloc(p, sizeof(int *));` --> `calloc(p, sizeof(int **));` (add *) or better `calloc(p, sizeof *arr);` which is less likely to get the type wrong. – chux - Reinstate Monica Oct 01 '15 at 14:31
  • `sizeof (arr)==sizeof(*arr)==sizeof(**arr)` certainly is true in your implementation. Yet, per spec, pointers of different types may differ in size. Different sized pointers is not uncommon between pointers to data, constant data, and functions. – chux - Reinstate Monica Oct 01 '15 at 16:05
  • @chux - I edited to take the sizeof a pointer, using `arr` as the pointer. I understand this is good to avoid type errors (in this case using `int ***arr;`). However, because sizeof (arr)==sizeof(*arr)==sizeof(**arr), I do not understand why you suggested sizeof(*arr)? (i.e., all of them are a sizeof a pointer, including sizeof(arr) in thes case ). Stated more succinctly, sizeof pointer to any type is still just the size of a pointer. Each will be different between different addressing architecture (eg 32 or 64 bit) but within same addressing, pointers to all types are same value. – ryyker Oct 01 '15 at 16:06
  • Sorry to comment again, bbut code should be `arr = calloc(p, sizeof *arr);`, `arr[x] = calloc(c ,sizeof *(arr[x]))`. – chux - Reinstate Monica Oct 01 '15 at 16:07
  • "sizeof pointer to any type is still just the size of a pointer" is not per C spec. Suggest posting it as a SO question if not convinced. – chux - Reinstate Monica Oct 01 '15 at 16:08
  • @chux - Please explain. when will the sizeof macro return a different value for sizeof int **, or int *** or int **a[]?. I don't think (with the exception of going to a different addressing architecture) that it ever will. Please explain why I am incorrect. (I am not trying to be obstinate, just trying to understand your reasoning) – ryyker Oct 01 '15 at 16:12
  • http://stackoverflow.com/questions/1241205/are-all-data-pointers-the-same-size-in-one-platform-for-all-data-types – chux - Reinstate Monica Oct 01 '15 at 16:16
  • @chux - very good link. Thank you, I was operating under a wrong assumption. – ryyker Oct 01 '15 at 16:34
0

You can allocate memory for buffer of items where each item is a two dimensional array. So it is effectively is a three dimensional array:

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

#define N 3

int main()
{
    int (*array)[N][N] = malloc(N * N * N * sizeof(int));

    /* set 0 to all values */
    memset(array, 0, N * N * N * sizeof(int));

    /* use as 3D array */
    array[0][0][0] = 1;
    array[1][1][1] = 2;
    array[2][2][2] = 3;

    int i;
    /* print array as contiguous buffer */
    for (i = 0; i < N * N * N; ++i)
        printf("i: %d\n", ((int*) array)[i]);

    free(array);

    return 0;
}

So, in memory the array is placed as regular int array[N][N][N].

Orest Hera
  • 6,706
  • 2
  • 21
  • 35
  • 1
    Note: `sizeof(int) * N * N * N * ` has an advantage over `N * N * N * sizeof(int)`: `int` multiplication could overflow, but possible not `(size_t)` multiplication. – chux - Reinstate Monica Sep 30 '15 at 20:38