7

How do you allocate and declare a 3D array of structs in C? Do you first allocate the array or declare it? I feel like you have to allocate it first so you can declare it so it is on the heap, but then how do you allocate something that hasn't been made yet? Also, should you allocate it all at once or element by element? Also am i putting the structs into the array correctly? My guess on how to do it would be:

header.h

struct myStruct{
   int a;
   int b;
}; 
typedef struct myStruct myStruct_t;

main.c

#include "header.h"
#include <stdio.h>
#include <stdlib.h>
int main(void){

    int length=2;
    int height=3;
    int width =4;
    myStruct_t *elements;
    struct myStruct arr = (*myStruct_t) calloc(length*height*width, sizeof(myStruct);
    //zero based array
    arr[length-1][height-1][width-1];

    int x=0;
    while(x<length){
        int y=0;
        while(y<height){
            int z=0;
            while(z<depth){
                arr[x][y][z].a=rand();
                arr[x][y][z].b=rand();
                z++;
            }
            y++;
        }
        x++;
    }
    return 0;
}    
dsb
  • 121
  • 12
  • Off-topic advise about using `.h` and `.c` files: http://stackoverflow.com/q/3482948/2186301 – yulian Feb 22 '15 at 18:22

2 Answers2

1

There are a couple of different ways to do this, depending on what you want. First, you can allocate your array on the stack (in C99 and some compilers) like this:

myStruct_t arr[length][height][depth];

If you want it allocated on the heap, then you can do a single allocation of the appropriate size. You can then either do the index calculation yourself or make a pointer do the work for you (in C99 and some compilers):

void *buf = malloc(length * height * width * sizeof(myStruct_t));
myStruct_t *arr = buf;
myStruct_t (*arr2)[height][width] = buf;

/* TODO: check return of malloc */
...

arr[x * height * width + y * width + z].a = rand();  /* indexing the C89 way */
arr2[x][y][z].b = rand();                            /* indexing the C99 way */

Or you can manually allocate the multiple dimensions.

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

typedef struct myStruct
{
  int a, b;

} myStruct_t;

int main()
{
  myStruct_t ***arr;
  int length = 5000, height = 1000, depth = 20;
  int x, y, z;
  int ret = 1;

  if (NULL == (arr = malloc(length * sizeof(myStruct_t**))))
    goto FAIL;

  for (x = 0; x < length; ++x)
  {
    if (NULL == (arr[x] = malloc(height * sizeof(myStruct_t*))))
      goto FAIL_X;

    for (y = 0; y < height; ++y)
    {
      if (NULL == (arr[x][y] = malloc(depth * sizeof(myStruct_t))))
        goto FAIL_Y;

      for (z = 0; z < depth; ++z)
      {
        arr[x][y][z].a = rand();
        arr[x][y][z].b = rand();
      }
    }
  }

  /* TODO: rest of program logic */

  /* program successfully completed */

  ret = 0;

  /* reclaim arr */

FAIL_CLEANUP: /* label used by TODO code that fails */

  for (x = length - 1; x >= 0; --x)
  {
    for (y = height - 1; y >= 0; --y)
    {
      free(arr[x][y]);
    FAIL_Y:
      ;
    }

    free(arr[x]);
  FAIL_X:
    ;
  }

  free(arr);

FAIL:    
  return ret;
}

This last version uses a lot more memory for all the explicit pointers it contains, its memory locality is worse and it's significantly more complex to properly allocate and reclaim. However, it does allow different sizes along your dimensions. For example, the array arr[0][4] can have a different size than arr[0][7] if you ever need that.

If you want to allocate it on the heap, then you probably want the second version with a single allocation and multi-dimension pointer (if available) or do the indexing yourself manually using appropriate math.

jschultz410
  • 2,849
  • 14
  • 22
  • Why myStruct**? Why two * instead of 3? If i wanted to allocate with the size of arr, would the line read arr = calloc(length, sizeof(struct ***arr)); ? – dsb Feb 22 '15 at 03:32
  • 1
    arr is declared as a struct myStruct ***. It does use 3 *'s. When you call calloc/malloc the return value has one more indirection versus the type you allocated. So, for example, the top level calloc returns a struct myStruct ***, because we allocated an array of struct myStruct **'s. When you use sizeof on an expression of arr, you don't add the struct back in. You would simply say sizeof(***arr), which would be equivalent to sizeof(struct myStruct). – jschultz410 Feb 22 '15 at 03:38
  • @jschultz410 You think that `malloc()` always magically works, and it never fails? Your code has potential undefined behavior, write safe code, and teach that. If the OP likes your code more, then one of these days you will be using one of his applications, and suddenly it will crash or something. – Iharob Al Asimi Feb 22 '15 at 12:33
  • @iharob Yes, you got me: I think malloc always magically works. My point was to show how to do the multiple dimensional allocation without cluttering it up with error handling. Of course, you always need to check for and handle errors in real programs. PS - embedding your error handling in the midst of your main line code like that is fugly. – jschultz410 Feb 22 '15 at 17:30
  • 1
    It is a huge mess of course, but I thought that otherwise the OP would be confused. – Iharob Al Asimi Feb 22 '15 at 17:43
  • There, I updated the code to handle errors and reclaim the multi-dimensional array. – jschultz410 Feb 22 '15 at 18:11
  • `struct myStruct (*arr2)[height][width] = (void*) arr;` – why? Why don't you assign the return value of `malloc()` **directly** to `arr2`? You certainly don't need that intermediate pointer… – The Paramagnetic Croissant Feb 22 '15 at 18:32
  • @TheParamagneticCroissant: I was showing him that you can access the array by either doing the offsetting yourself manually (through arr; works for C89) or through an appropriately typed pointer (through arr2). I did the cast to (void*) just because I don't recall the correct way to cast to a multi-dimensional pointer. Is it arr2 = (struct myStruct (*)[height][width]) arr? I don't recall. – jschultz410 Feb 22 '15 at 18:40
  • @jschultz410 yes, that's it, although in that case initializing a common base pointer of type `void *` and then assigning it to two different pointers (a pointer-to-struct and a pointer-to-array, respectively) would be less confusing. – The Paramagnetic Croissant Feb 22 '15 at 19:00
  • @jschultz410 that's a lot better. – Iharob Al Asimi Feb 22 '15 at 19:05
  • @jschultz410 VLA is an optional feature in c11. Even then, it's not available for any version of visual studio. – Forss Feb 22 '15 at 20:12
  • @Forss That sucks. Good thing I also gave the C89 ways to do this! – jschultz410 Feb 22 '15 at 20:53
1

The easy way is:

 myStruct_t (*arr2)[height][width] = calloc( length * sizeof *arr );

Then your loop can access arr2[x][y][z].a = rand(); and so on. If you're not familiar with this way of calling calloc, see here. As usual with malloc, check arr2 against NULL before proceeding.

The triple-pointer approach is not really a practical solution. If your compiler does not support variably-modified types then the array should be flattened to 1-D.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    As @Forss noted, variable length arrays (and pointers to such) were made an optional feature in C11 unfortunately. – jschultz410 Feb 22 '15 at 22:19
  • @jschultz410 that doesn't mean they shouldn't be used where available. Nice code can work on all the good compilers, and if it has to be run on C89 or lazy-vendor-C11 then you can use an ugly option (where I recommend flattening and not triple-pointering) – M.M Feb 22 '15 at 22:28
  • Yeah, I completely agree. I just find it a shame that the C11 standard made a previously mandatory portion (VLAs in C99) now optional. Visual Studio* is pretty widely used on Windows and this code won't compile on it AFAIK. – jschultz410 Feb 22 '15 at 22:31
  • @jschultz410 Political pressure from MS no doubt. Same with Annex K. – M.M Feb 22 '15 at 22:36