2

This question has been answered here but for me some things are still left open and the discussion is too old for anyone to reply to comments, besides I wanted to ask what was wrong in my implementation.

Anyway, the problem setting. I want to allocate space for a 3D array of dimensions 3, h, w. What I am doing:

int ***a = (int***)malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++)
{
   a[i] = (int **) malloc(height * sizeof(int*)); 
}

I understand this as creating first space for three 3 int** and then allocating height int**; Then :

for (int i = 0; i < 3; i++)
{
  a[i][0] = (int *) malloc(height * width * sizeof(int *));
  for (int j = 1; j < height; j++)
   {
     a[i][j] = a[i][j-1] + width;
   }
}

So now I think that each a[i][0] is basically space for a 2D array of sizes height and width; furthermore the rest of a[i][j] for j != 0 is initialized to the j th row of a[i][0];

then I initialize values by:

for (int c = 0; c < 3; c++){
  for (int i = 0; i < height; i++){
    for (int j = 0; j < width; j++){
      a[c][i][j] = i+j+c;

This however is incorrect. I wanted to do this as an extension exercise to allocating a 2D array in the following way:

int *a[height]; 
a[0] = (int*)malloc(height * width * sizeof(int));
for (int i = 1; i < height; i++){
  a[i] = a[i-1] + widht;
}

This way a is an array of pointers of length height, a[0] is allocated for the whole 2D array and the consequent pointers point to their respective rows and I can write to them simply by calling a[i][j] = some number;

What I want to do is extend this 2D idea to the 3D case, but I am sadly failing.

Community
  • 1
  • 1
Vahagn Tumanyan
  • 500
  • 1
  • 13
  • 29
  • `int (*a)[height][width] = malloc(3 * sizeof *a);` should give you a 3d array. – mch Jan 18 '17 at 15:27
  • wouldn't this create a pointer to a 2D array of sizes height , width? – Vahagn Tumanyan Jan 18 '17 at 15:30
  • yes, `a` is a pointer to a 2d array and can be used with `a[i][j][k]`. – mch Jan 18 '17 at 15:34
  • Why can't you just do a 1D-allocation, and do the indexing yourself? Much more compact, and I'm not convinced the two multiplications are more expensive than all that memory-traversing. Not to mention the more efficient heap usage. – unwind Jan 18 '17 at 15:58
  • 1
    @unwind that _would_ be the better way to deal with this, however, all I wanted was some practice with the tricky part of memory management, and array to pointer decay and whatnot. – Vahagn Tumanyan Jan 18 '17 at 15:59
  • @VahagnTumanyan Pointer-to-pointers are not some "tricky part of memory management". The only case where you would use pointer-to-pointers as you attempt here, is when you need an array of pointers, where each array item points to an array of variable length. Such an item is _not_ an array, but a look-up table. Whenever you use true arrays, you should _always_ allocate all memory in one go, so that it ends up in adjacent memory cells. This reduces heap fragmentation and utilizes data cache optimally. – Lundin Jan 18 '17 at 16:04
  • WARNING: There's a lot of rotten programming books, stinking internet tutorials and mouldy teachers preaching pointer-to-pointers for dynamic allocation of multi-dimensional arrays. They are incorrect and incompetent, don't listen to them. This very topic has been debated endlessly on SO. – Lundin Jan 18 '17 at 16:05
  • @VahagnTumanyan Creating an n-dimensional "array" with a single call to `malloc()` will test your ability to deal with memory a lot more thoroughly than creating such an "array" with a `malloc()` call in every loop iteration. One hard part that's often overlooked is the need to ensure the "data element" portion of the single `malloc()`'d block of memory is properly aligned per the standard `malloc()` requirements. – Andrew Henle Jan 18 '17 at 16:13
  • @Lundin *Whenever you use true arrays, you should always allocate all memory in one go, so that it ends up in adjacent memory cells. This reduces heap fragmentation and utilizes data cache optimally.* It's also a tremendously faster. But, doing it for an arbitrary n-dimensional "array" of arbitrary dimensions known only at run-time isn't easy - imagine an implementation with 4-byte pointers that requires 8- or 16-byte alignment to properly meet all the alignment requirements for `malloc()`'d memory. The "`malloc()` in the loop" method may be inefficient, but it's a lot less bug-prone. – Andrew Henle Jan 18 '17 at 16:19
  • @AndrewHenle That's quite easy to fix. If you suspect that malloc doesn't give you aligned memory chunks, you simply embed your data in structs and then the problem is solved. On most systems I don't think this is an issue though. Systems that use malloc in the first place are typically high-end 32 CPUs or larger. Exotic microcontrollers or DSPs tend not to use malloc at all. – Lundin Jan 18 '17 at 18:43

3 Answers3

5
int ***a = (int***)malloc(3 * sizeof(int*));

This doesn't allocate anything meaningful. In particular, it does not allocate a 3D array, or even a part of a 3D array. It allocates space for 3 integer pointers, which doesn't make any sense. Neither does a int*** make any sense.

Instead, allocate a 3D array:

int (*a)[y][z] = malloc( sizeof(int[x][y][z]) );

for(int i=0; i<x; i++)
  for(int j=0; j<y; j++)
    for(int k=0; k<z; k++)
      a[i][j][k] = something;

free(a);

EDIT (any sarcastic tone in the below text is fully intentional)

For the benefit of the three-star programmers who refuse to accept any solution with less than three levels of indirection, here is an example of how to properly do three-star programming.

Unfortunately it does not yet contain non-conditional branching upwards, memory leaks or complex pointer arithmetic. But for a three-star programmer, such joyful things are of course trivial to add later. It does however guarantee data cache misses, so that the program will run with equal performance as a system without any data cache.

One may also consider to pass the allocated pointer-to-pointer-to-pointer as a parameter, thus achieving 4 stars!!!

[As in, the pointer-to-pointer method is very bad practice, but if you for reasons unknown insist on using it, you might as well do it proper.]

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


void free_int_3d (int*** ppp, size_t X, size_t Y, size_t Z)
{
  (void)Z;

  if(ppp == NULL)
  {
    return ;
  }

  for(size_t x=0; x < X; x++)
  {
    if(ppp[x] != NULL)
    {
      for(size_t y=0; y < Y; y++)
      {
        if(ppp[x][y] != NULL)
        {
          free(ppp[x][y]);
        }
      }
      free(ppp[x]);
    }
  }
  free(ppp);
}


#define malloc_with_cleanup(ptr, size)     \
ptr = malloc(size);                        \
if(ptr == NULL)                            \
{                                          \
  free_int_3d(result, X, Y, Z);            \
  return NULL;                             \
}

int*** int_alloc_3d (size_t X, size_t Y, size_t Z)
{
  int*** result = NULL;

  malloc_with_cleanup(result, sizeof(int**[X]));
  for(size_t x=0; x<X; x++)
  {
    malloc_with_cleanup(result[x], sizeof(int*[Y]));
    for(size_t y=0; y<Y; y++)
    {
      malloc_with_cleanup(result[x][y], sizeof(int[Z]));
    }
  }
  return result;
}


int main (void)
{
  const size_t X = 5;
  const size_t Y = 3;
  const size_t Z = 4;

  int*** ppp = int_alloc_3d(X, Y, Z);

  for(size_t i=0; i<X; i++)
  {
    for(size_t j=0; j<Y; j++)
    {
      for(size_t k=0; k<Z; k++)
      {
        ppp[i][j][k] = (int)k;
        printf("%d ", ppp[i][j][k]);
      }
      printf("\n");
    }
    printf("\n");
  }

  free_int_3d(ppp, X, Y, Z);

  return 0;
}

Output:

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3

0 1 2 3
0 1 2 3
0 1 2 3
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I know it doesn't allocate the whole array, what I wanted to do there was to allocate 3 pointers of type `int**` – Vahagn Tumanyan Jan 18 '17 at 15:36
  • @VahagnTumanyan Why? You said you wanted a 3D array. Pointer-to-pointers aren't meaningful in that context. – Lundin Jan 18 '17 at 15:36
  • Simply as an exercise for dealing with several pointers of pointers of any dimensions. And also, use the more correct syntax (semantics?) for `malloc`, as in casting the returned `void *` to the correct type of pointer and having `(number_of_elems * sizeof(type))` rather than simply allocating an array. – Vahagn Tumanyan Jan 18 '17 at 15:39
  • @VahagnTumanyan Pointers are not arrays. Arrays are not pointers. Arrays happen to decay into a pointer to the first element when used in an expression, but that doesn't make pointers arrays. There is no relation whatsoever between a pointer-to-pointer and arrays. Casting the returned type from malloc is widely recognized as bad/pointless practice in C, see [the most FAQ on Stack Overflow C of all time](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). – Lundin Jan 18 '17 at 15:43
  • Alright. thanks. However something like `int (*a)[y][z] = malloc( sizeof(int[x][y][z]) );` doesn't compile for me on gcc, it tells that I have an illegal conversion from `void *` to `(*)[][]` – Vahagn Tumanyan Jan 18 '17 at 15:49
  • 1
    @VahagnTumanyan Make sure that you 1) aren't actually using a C++ compiler, in which case you will get weird errors, and 2) that you aren't accidentally using an 27 years old version of gcc, by adding either the compiler option `-std=c99` or `-std=c11`. – Lundin Jan 18 '17 at 15:51
  • Call me a three-star programmer; I still think it is fun to create 3D arrays using pointers to pointers to pointers and `malloc`ing them all separately. – Greg Schmit Jan 20 '17 at 15:01
  • 1
    @GregSchmit Again, these are not 3D arrays, since the memory is not allocated adjacently. It is a look-up table of look-up tables pointing at data. Personally, I don't quite see why slow programs are more fun than a fast program doing the same task. My idea of fun is rather to benchmark the two snippets above, then get a good laugh when I measure execution time of the second one. – Lundin Jan 20 '17 at 15:14
  • @Lundin You still aren't reading very thoroughly. I didn't say that it was more fun to write programs that are slower, did I? Rather I said that it is fun to implement a three-level pointer correctly, especially when you start out not really having a good understanding of pointers. That those programs are slower than a contiguous memory block is a good argument to not implement them in real software, but that doesn't take away the value of implementing them for the purposes of bettering one's understanding of pointers, pointer arithmetic, and memory allocation. – Greg Schmit Jan 20 '17 at 15:25
  • @Lundin Serious question though since I am not too experienced with C. You declared the data as `*a[y][z]` and did one memory allocation. Is `a` a triple pointer, or just a single pointer when you do that? As in, would dereferencing it more than once make any sense? – Greg Schmit Jan 20 '17 at 15:30
  • @GregSchmit The most correct form would be `int (*a)[x][y][z]` - a pointer to a 3D array. But that form is inconvenient, since that pointer has to be de-referenced like any other pointer, when you want to access the contents (the 3D array). That is: `(*a)[i][j][k]`. This syntax is tedious, so therefore there's a trick to point at the first item of the 3D array instead - which is a 2D array. Then you can do `a[i][j][k]`. [Detailed explanation](http://stackoverflow.com/a/32050859/584518). – Lundin Jan 20 '17 at 15:41
  • @Lundin I also now remember another reason I preferred the raw pointer way. If you need to pass your array to another method, that method has to know the `y` and `z` dimension, right? At least that is how I have to set it up to make it work. I cannot just pass it as a triple pointer. Is my understanding correct? – Greg Schmit Jan 26 '17 at 03:25
  • @GregSchmit In general, functions in C always have to know the array size, no matter what kind of array it is. For example `void func (size_t x, int array[x])`. Pointer-to-pointer syntax doesn't change that. If your function for some reason already know the array size, then I would strongly suspect that poor program design is the reason. – Lundin Jan 26 '17 at 09:08
  • @Lundin Oh, cool I didn't know that was the standard way to pass size (I would usually pass it as a second parameter). Your way makes sense though. – Greg Schmit Jan 26 '17 at 17:09
  • @Lundin great answer! Make me laugh and teach me a lot in the same time. +1 from me. – Ondrej Mar 31 '18 at 21:43
  • lets say i am working with a global 3d matrix how does one declare such variable length array out of file scope. Normally for a 3D pointer i declare it as follows `double ***a` and `extern double ***a` – datapanda Aug 03 '18 at 14:47
3

To solve OP's higher goal, I suspect a pointer to a 2D array would suffice @mch

int (*a)[height][width] = malloc(sizeof *a * 3);

 a[c][i][j] = i + j + c;

But per OP's request....

... to allocate space for a 3D array ...

How would code typically create a 3D array?
The below code does that using a variable length array available in C99.
(VLA is optionally support in C11)

int a1[3][height][width];
// and assign it accordingly
for (int c = 0; c < 3; c++) {
  for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
      a1[c][i][j] = i + j + c;

Yet OP wants to allocate space for such an array.

malloc() returns a pointer. A pointer to allocated memory. So we need to create a pointer to such an array. Once we have done that, allocation and element assignment is easy.

int (*a2)[3][height][width] = malloc(sizeof *a2);

// 3 nested for loop as above
    (*a2)[c][i][j] = i + j + c;

Example code

void VT_3_dimensional_array(int height, int width) {
  assert(height > 0 && width > 0);

  int (*a2)[3][height][width] = malloc(sizeof *a2);
  printf("%p %zu\n", (void*) a2, sizeof *a2);
  if (a2 == NULL) {
    perror("Out of memory");
    exit(EXIT_FAILURE);
  }
  for (int c = 0; c < 3; c++) {
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        (*a2)[c][i][j] = i + j + c;
      }
    }
  }

  // use a;
  for (int c = 0; c < 3; c++) {
    for (int i = 0; i < height; i++) {
      putchar('(');
      for (int j = 0; j < width; j++) {
        printf(" %X", (*a2)[c][i][j]);
      }
      putchar(')');
    }
    puts("");
  }

  free(a2);
}

int main() {
  VT_3_dimensional_array(5, 7);
  return 0;
}

output

0x80071980 420
( 0 1 2 3 4 5 6)( 1 2 3 4 5 6 7)( 2 3 4 5 6 7 8)( 3 4 5 6 7 8 9)( 4 5 6 7 8 9 A)
( 1 2 3 4 5 6 7)( 2 3 4 5 6 7 8)( 3 4 5 6 7 8 9)( 4 5 6 7 8 9 A)( 5 6 7 8 9 A B)
( 2 3 4 5 6 7 8)( 3 4 5 6 7 8 9)( 4 5 6 7 8 9 A)( 5 6 7 8 9 A B)( 6 7 8 9 A B C)

Note, technically OP's code is using the wrong size. Usually sizeof(int*) will be the same as sizeof(int**), so no great harm. Yet it does indicate confusion about the allocation.

// wrong type  -----------------------v  should be int**
int ***a = (int***)malloc(3 * sizeof(int*));
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I wonder who downvoted this. Seems perfectly legit to me. But I guess, you might have stopped that downvoter by moving that last paragraph up to the top and proceed with the explanation later. – cmaster - reinstate monica Jan 18 '17 at 16:50
  • @cmaster OK answer amended. – chux - Reinstate Monica Jan 18 '17 at 17:03
  • 1
    What is gained by using a pointer to a 3d VLA and `malloc`ing space instead of just declaring a 3d VLA in the first place? The `malloc`ed array could be larger, I suppose. – ad absurdum Jan 18 '17 at 17:05
  • 1
    @DavidBowling A VLA failure is not detectable, a `malloc()` failure is. I see no great value in either approach above, preferring a simple `int *a = malloc(sizeof *a * 3 * height * width);`. Yet OP asked how to allocate space for a 3D array, so this answer focuses on that. – chux - Reinstate Monica Jan 18 '17 at 18:03
3

Let's visualize what you're trying to do first, and then I'll show the code to get there:

  int ***         int **                int *                  int 

  +---+           +---+                 +---+                  +---+
a |   | ---> a[0] |   | ------> a[0][0] |   | ----> a[0][0][0] |   |
  +---+           +---+                 +---+                  +---+
             a[1] |   | ----+   a[0][1] |   | -+    a[0][0][1] |   |
                  +---+     |           +---+  |               +---+
             a[2] |   |     |            ...   |                ...
                  +---+     |           +---+  |               +---+
                            | a[0][h-1] |   |  |  a[0][0][w-1] |   | 
                            |           +---+  |               +---+
                            |                  |
                            |           +---+  |               +---+
                            +-> a[1][0] |   |  +--> a[0][1][0] |   |
                                        +---+                  +---+
                                a[1][1] |   |       a[0][1][1] |   |
                                        +---+                  +---+
                                         ...                    ...
                                        +---+                  +---+
                              a[1][h-1] |   |     a[0][1][w-1] |   |
                                        +---+                  +---+

Types:

  • Each a[i][j][k] has type int;
  • Each a[i][j] points to the first element of a sequence of int objects, so it must have type int *;
  • Each a[i] points to the first element of a sequence of int * objects, so it must have type int **;
  • a points to the first element of a sequence of int ** objects, so it must have type int ***.

Since you're doing a piecemeal nested allocation, you need to check the result of each malloc call, and if there's an error, you will want to clean up any previously allocated memory before bailing out, otherwise you risk memory leaks. Unfortunately, there's no really clean or elegant way to do that - you either carry a flag around and do a bunch of extra tests, or you throw in a couple of gotos. I'm going to show two different approaches, neither of which is all that pretty.

First method - as we allocate each a[i], we also allocate each a[i][j] (a "depth-first" approach) and initialize each a[i][j][k]. If an allocation on a[i][j] fails, we must deallocate all of a[i][0] through a[i][j-1], then deallocate a[i], then repeat the process for each of a[0] through a[i-1]:

/**
 * Depth-first approach: allocate each a[i][j] with each a[i]
 */
int ***alloc1( size_t pages, size_t height, size_t width )
{
  size_t i, j, k;

  int ***a = malloc( sizeof *a * pages ); // allocate space for N int **
                                          // objects, where N == pages

  if ( !a )
    return NULL;

  for ( i = 0; i < pages; i++ )
  {
    a[i] = malloc( sizeof *a[i] * height );  // for each a[i], allocate space for
    if ( !a[i] )                             // N int * objects, where N == height
      goto cleanup_1;

    for ( j = 0; j < height; j++ )
    {
      a[i][j] = malloc( sizeof *a[i][j] * width ); // for each a[i][j], allocate      
      if ( !a[i][j] )                              // space for N int objects,
        goto cleanup_2;                            // where N == w

      for ( k = 0; k < width; k++ )
        a[i][j][k] = initial_value( i, j, k );
    }
  }
  goto done;

/**
 * Free all of a[i][0] through a[i][j-1], then free a[i]
 */
cleanup_2:
  while ( j-- )
    free( a[i][j] );
  free( a[i] );

/**
 * Free all of a[0] through a[i-1], then free a
 */
cleanup_1:
  while ( i-- )
  {
    j = height;
    goto cleanup_2;
  }
  free( a );
  a = NULL;

done:
  return a;  // at this point, a is either a valid pointer or NULL.
}

Yes, this code contains gotos, and it violates one of my pet rules (never branch backwards). But, there's a fairly clean separation between the allocation code and the cleanup code, and we don't repeat ourselves in the cleanup sections. cleanup_2 deallocates all the rows within a page, along with the page itself; cleanup_1 deallocates all of the pages. cleanup_2 "falls through" into cleanup_1.

Here's a second method - first we allocate all a[i] before allocating any a[i][j], then we make sure all a[i][j] were successfully allocated before initializing the array contents.

/**
 * Breadth-first approach; allocate all a[i], then all a[i][j], then initialize
 */
int ***alloc2( size_t pages, size_t height, size_t width )
{
  size_t i, j, k;

  /**
   * Allocate space for N int ** objects, where N == pages
   */
  int ***a = malloc( sizeof *a * pages );

  if ( !a )
    return NULL;                        // allocation failed for initial sequence, return NULL

  for ( i = 0; i < pages; i++ )  // For each a[i], allocate N objects of type
  {                              // int *, where N == height
    a[i] = malloc( sizeof *a[i] * height );
    if ( !a[i] )
      break;
  }

  if ( i < pages )
  {
    while ( i-- )                       // allocation of a[i] failed, free up a[0] through a[i-1]
      free( a[i] );
    free( a );                          // free a
    return NULL;
  }

  for ( i = 0; i < pages; i++ )    
  {
    for ( j = 0; j < height; j++ )
    {
      a[i][j] = malloc( sizeof *a[i][j] * width ); // for each a[i][j], allocate    
      if ( !a[i][j] )                             // space for N int objects, 
        break;                                    // where N == width
    }
  }

  if ( j < h )
  {
    do
    {
      while ( j-- )                     // allocation of a[i][j] failed, free up a[i][0] through a[i][j-1]
        free( a[i][j] );                // repeat for all of a[0] through a[i-1].
      free( a[i] );
      j = h;
    } while ( i-- );
    free( a );                          // free a
    return NULL;
  }

  /**
   * All allocations were successful, initialize array contents
   */    
  for ( i = 0; i < pages; i++ )
    for ( j = 0; j < height; j++ )
      for ( k = 0; k < width; k++ )
        a[i][j][k] = initial_value( i, j, k );

  return a;
}

No gotos! But the code doesn't "flow" as well IMO. There's not as clean a separation between allocation and cleanup code, and there's a bit of repetitiveness between the cleanup sections.

Note that for both methods, the memory is not contiguous - the element immediately following a[0][0][h-1] is not going to be a[0][1][0]. If you need all your array elements to be adjacent in memory, you will need to use the method shown by the others:

int (*a)[h][w] = malloc( sizeof *a * 3 );

with the caveat that if h and w are large, you may not have enough contiguously allocated memory to satsify the request.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Note that the picture does not show a 3D array, but a number of scattered segments pointed at by a look-up table. So if you want a 3D array, this is not the correct solution. In addition, combining three-star programming with spaghetti programming might not be the best idea ever. Instead of spaghetti gotos, you can simply initialize all pointers to NULL before calling malloc. That way, you can pass them to `free()` no matter if they point at allocated memory or not. Only one clean-up routine is needed then. – Lundin Jan 20 '17 at 07:47
  • @Lundin: The OP wanted an example of *non-contiguous* allocation extended to 3 dimensions, which is what I provided. – John Bode Jan 20 '17 at 12:55
  • I suspect that the question is about an XY problem. The OP asks how to use pointer-to-pointer look-up tables for dynamic memory allocation. While the actual problem they are trying to solve is probably just "how to allocate a 3D array dynamically". – Lundin Jan 20 '17 at 12:59