Search code examples
cmultidimensional-arrayc99flexible-array-member

2-dimensional array in a struct in C


I'm trying to initialize a 2-dimensional array in a structure but I always get an error :

gcc -g -Wall -W -I/usr/include/SDL   -c -o fractal.o fractal.c
In file included from fractal.c:2:0:
fractal.h:12:12: error: array type has incomplete element type ‘double[]’
     double values[][];

Here's the code:

struct fractal {

    char name[64];
    int height;
    int width;
    double a;
    double b;
    double meanValue;       
    double values[][];  /*This line is causing the error*/
 };

Ideally I'd like to initialize the height and width of the 2-dimensional array like this:

struct fractal {

    /*... Same code as above ...*/      

    double values[width][height];  
 };

But then I get two other errors when compiling:

gcc -g -Wall -W -I/usr/include/SDL   -c -o fractal.o fractal.c
In file included from fractal.c:2:0:
fractal.h:12:19: error: ‘width’ undeclared here (not in a function)
     double values[width][height];
                   ^
fractal.h:12:26: error: ‘height’ undeclared here (not in a function)
     double values[width][height];
                          ^

I've looked about everywhere but my code should work and I can't figure out why it doesn't.

Thanks for the help


Solution

  • As a disclaimer, this is something of an advanced topic, so if you are a beginner you might want to just back away from it entirely and just use a double* array followed by a call to malloc for each pointer. (Fine for beginners, unacceptable in professional code.)

    It is an advanced topic since this particular case is a weakness in C. The feature you are trying to use, with an empty array at the end of a struct, is known as flexible array member. This only works for one dimension however. If both dimensions are unknown at compile time, you have to come up with a work-around.

    The allocation part is as for any flexible array member: allocate the struct dynamically and make size for the trailing array.

    fractal_t* f = malloc(sizeof *f + sizeof(double[height][width]) );
    

    (In this case taking advantage of the convenient VLA syntax, although a flexible array member is not a VLA.)

    Technically, the last member of the struct is supposedly double[] now, or so says the struct declaration. But memory returned by malloc has no actual effective type until you access it, after which the effective type of that memory becomes the type used for the access.

    We can use this rule to access that memory as if it was a double[][], even though the pointer type in the struct is a different one. Given a fractal f, the code for accessing through a pointer becomes something like this:

    double (*array_2D)[width] = (double(*)[width]) f->values;
    

    Where array_2D is an array pointer. The most correct type to use here would have been an array pointer to an array of double, double (*)[height][width], but that one comes with mandatory ugly accessing (*array_2D)[i][j]. To avoid such ugliness, a common trick is to leave out the left-most dimension in the array pointer declaration, then we can access it as array_2D[i][j] which looks far prettier.

    Example code:

    #include <stdlib.h>
    #include <stdio.h>
    
    typedef struct 
    {
      char name[64];
      size_t height;
      size_t width;
      double a;
      double b;
      double meanValue;       
      double values[];
    } fractal_t;
    
    
    fractal_t* fractal_create (size_t height, size_t width)
    {
      // using calloc since it conveniently fills everything with zeroes
      fractal_t* f = calloc(1, sizeof *f + sizeof(double[height][width]) );
      f->height = height;
      f->width = width;
      // ...
      return f;
    }
    
    void fractal_destroy (fractal_t* f)
    {
      free(f);
    }
    
    void fractal_fill (fractal_t* f)
    {
      double (*array_2D)[f->width] = (double(*)[f->width]) f->values;
    
      for(size_t height=0; height < f->height; height++)
      {
        for(size_t width=0; width < f->width; width++)
        {
          array_2D[height][width] = (double)width; // whatever value that makes sense
        }
      }
    }
    
    void fractal_print (const fractal_t* f)
    {
      double (*array_2D)[f->width] = (double(*)[f->width]) f->values;
    
      for(size_t height=0; height < f->height; height++)
      {
        for(size_t width=0; width < f->width; width++)
        {
          printf("%.5f ", array_2D[height][width]); 
        }
        printf("\n");
      }
    }
    
    int main (void)
    {
      int h = 3;
      int w = 4;
    
      fractal_t* fractal = fractal_create(h, w);
      fractal_fill(fractal); // fill with some garbage value
      fractal_print(fractal);
      fractal_destroy(fractal);
    }