3

I am attempting to implement a grid of cells, analogous to that of Conway's game of life.

While each individual grid should have fixed size in both dimensions, I would like a Grid struct that allows for any size in both dimensions.

This is in analogy to how arrays can be of any size, but an array once initialized has a fixed size.

This is what I have so far:

typedef struct Cell {
    int data;
    // stuff to be added later
} Cell;

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell cell[][];
} Grid;

Grid initGrid(unsigned width, unsigned height) {
    Grid g;
    g.width = width;
    g.height = height;
    g.cell = malloc( sizeof(Cell)*width*height );
    return g;
}

However I get the following compile-time error:

main.c|12|note: declaration of `‘cell’ as multidimensional array must have bounds for all dimensions except the first|

How can I define a Grid data type with flexible size?

Post scriptum: as a C newbie, I thought the following would work:

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell cell[width][height];
} Grid;

Post post scriptum: I am always uneasy whenever I use malloc. Am I doing (or trying to do) anything horribly wrong here?

alk
  • 69,737
  • 10
  • 105
  • 255
Riccardo Orlando
  • 207
  • 2
  • 10
  • 1
    no you cannot have so powerful VLAs in C. That's a pity. – Jean-François Fabre Mar 15 '18 at 13:01
  • 4
    If you don't know either dimension just replace `Cell cell[][];` by `Cell *cell;` and use pointer arithmetic. In C, 2-dimensional arrays are little more than syntactic sugar for a special use of 1-dimensional arrays. – John Coleman Mar 15 '18 at 13:01
  • 1
    You're going to need to user a pointer to a pointer, with at least one call of `malloc()`, to emulate the behaviour of a multi-dimensional array. Either that, or use a dynamically allocated one-dimensional array of size `width*height` and use (say) `cell[i + height * j]` rather than `cell[i][j]` to access elements. – Peter Mar 15 '18 at 13:04
  • Ignore everyone telling you to use some sort of pointer solution with an extra malloc call - they haven't understood the problem. Such data won't be allocated adjacently like a flexible array member. – Lundin Mar 15 '18 at 15:26

4 Answers4

6

You can't do it with double indexing (cell[x][y]) in C, there's no way to express that the number of bytes to jump for each row is dynamic.

So, the best (in my opinion) way to do is it to just do the indexing manually, using a one-dimensional array.

Put a plain:

Cell *cell;

in the struct (keeping width and height) and then index like so:

set_cell(Grid *g, unsigned int x, unsigned int y, Cell value)
{
  g->cell[y * g->width + x] = value;
}

it's not unlikely that the compiler will inline this, and it's going to be pretty tight. Probably faster than the jagged array" approach which uses much more memory and another layer of indirection.

Allocation is simple:

Grid initGrid(unsigned int width, unsigned int height)
{
    Grid g;
    g.width = width;
    g.height = height;
    g.cell = malloc(width * height * sizeof *g.cell);
    // add assert or error here, can't return NULL for value type
    return g;
}

if you wanted to heap-allocate Grid too, you could co-allocate it with its elements.

And yes, you need to free() the allocation when you're done with it, in order to not leak memory. Strictly speaking on modern systems the OS will free all resources when the program ends anyway, but it's good form to free anyway:

void destroyGrid(Grid g)
{
  free(g.cell);
}
unwind
  • 391,730
  • 64
  • 469
  • 606
  • Thank you. Do I need to `malloc` the cell array? If so, do I then have to `free` it at the end of `main`? – Riccardo Orlando Mar 15 '18 at 13:21
  • There is no way to express the number of elements in a row is dynamic **in a struct**. It can be done in C, outside of a struct, and there are various ways that can be used in conjunction with a struct. – Eric Postpischil Mar 15 '18 at 13:27
  • 1
    @EricPostpischil No, there's no way to express it that ends up with 2-dimensional array indexing working without an explicit intermediate pointer array (a "jagged array"). – unwind Mar 15 '18 at 13:32
  • `#include `, `#include `, `int main(int argc, char *argv[]) { int R = atoi(argv[1]), C = atoi(argv[2]); int A[R][C]; for (int r = 0; r < R; ++r) for (int c = 0; c < C; ++c) A[r][c] = r*c; for (int r = 0; r < R; ++r, printf("\n")) for (int c = 0; c < C; ++c) printf(" %2d", A[r][c]); }` compiles and runs with Apple LLVM 9.0.0 clang-900.0.39.2, uses variable length arrays as specified in C 2011. – Eric Postpischil Mar 15 '18 at 13:40
  • @EricPostpischil A right, good point, VLAs can be 2D but can't live inside structs of course. Thanks for clarifying. – unwind Mar 15 '18 at 13:50
1

following this excelent post: How do I work with dynamic multi-dimensional arrays in C? read @JensGustedt post and follow his link variable length arrays (VLAs)

there is actually a way - I followed his post and written a small test program to verify:

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

int main(int argc, char ** argv)
{
    unsigned int height = 100;
    unsigned int width = 10;

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

    array[90][2] = 1231;
    printf("%d", array[90][2]);
}

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

int main(int argc, char ** argv)
{
    unsigned int height;
    unsigned int width;
    int i,j;

    printf("enter width: ");
    scanf("%d", &width);
    printf("enter height: ");
    scanf("%d", &height);
    int (*array)[width] = malloc (sizeof(int[height][width]));

    for (i = 0; i < height; i++ )
        for (j = 0; j < width; j++ )
            array[i][j] = i;

    for (i = 0; i < height; i++ ) {
        for (j = 0; j < width; j++ )
            printf("%d ", array[i][j]);
        printf("\n");
    }
}

and the console:

enter width: 10
enter height: 6
0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 
2 2 2 2 2 2 2 2 2 2 
3 3 3 3 3 3 3 3 3 3 
4 4 4 4 4 4 4 4 4 4 
5 5 5 5 5 5 5 5 5 5 

I'll admit it suprising - I was not aware this exists...


EDIT - using structs:

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

typedef struct Cell {
    int data;
    // stuff to be added later
} Cell;

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell *cell;
} Grid;

Grid initGrid(unsigned width, unsigned height) {
    Grid g;
    g.width = width;
    g.height = height;
    g.cell = malloc( sizeof(Cell[height][width]) );
    return g;
}
int main(int argc, char ** argv)
{
    unsigned int height;
    unsigned int width;
    int i,j;
    Grid test;

    printf("enter width: ");
    scanf("%d", &width);
    printf("enter height: ");
    scanf("%d", &height);
    test = initGrid (width, height);
    Cell (*array)[width] = test.cell;

    for (i = 0; i < height; i++ )
        for (j = 0; j < width; j++ )
            array[i][j].data = i;

    for (i = 0; i < height; i++ ) {
        for (j = 0; j < width; j++ )
            printf("%d ", array[i][j].data);
        printf("\n");
    }
}

console output:

enter width: 20
enter height: 10
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 
4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 
5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 
6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 
7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 
8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 

there is a casting warning which i did not have time to resolve, but one can implement the idea - just do it cleanly... again it's a POC not an actual program

Omer Dagan
  • 662
  • 5
  • 14
  • That’s not dynamic. Your program works because `width` is known at compile-time. – bfontaine Mar 15 '18 at 13:47
  • @bfontaine thanks for the response, but at least in this case you are wrong... watch my edit... – Omer Dagan Mar 15 '18 at 14:01
  • 1
    Now do it *without* forcing it into a VLA with `Cell (*array)[width] = test.cell;`. And enable all your compiler warnings. – Andrew Henle Mar 15 '18 at 14:40
  • First, I mentioned the warning , I don't think that's solvable - but one can live with it in certain conditions. Second, it can't be done without the force casting, but the added benefit of readability might offset the warning. I won't use this solution in my code but it's nice to know it exist – Omer Dagan Mar 15 '18 at 14:46
  • What does this have to do with flexible array members? I don't see how this answers the question. – Lundin Mar 15 '18 at 15:23
0

You are pretty much out of luck here, as there is no way in C to use variable array lengths within a struct definition. What you can do is this:

typedef struct Grid {
    unsigned width, height;
    void* cell_internal;    //Type: Cell(*)[height]
} Grid;

#define cell(myGrid) ((Cell(*)[(myGrid).height])(myGrid).cell_internal)

//in the constructor of Grid
newGrid->width = ...;
newGrid->height = ...;
cell(*newGrid) = malloc(newGrid->width*sizeof(*cell(*newGrid)));
for(unsigned x = 0; x < newGrid->width; x++) {
    for(unsigned y = 0; y < newGrid->height; y++) {
        cell(*newGrid)[x][y] = ...;
    }
}

This is a dirty little hack, but it should work fine. The cool part is, that you can simply address your grid cells with cell(aGrid)[x][y]. The downside is, that it does obscure what is actually going on quite thoroughly. And there are not many people who can actually read what the cell() macro does. (Hint: It simply casts the void* to a pointer to a column array Cell(*)[myGrid.height], whatever myGrid.height may be at that point in time.)


Of course, you can go more explicit like this:

typedef struct Grid {
    unsigned width, height;
    void* cell_internal;    //Type: Cell(*)[height]
} Grid;

//in the constructor of Grid
newGrid->width = ...;
newGrid->height = ...;
Cell (*cells)[newGrid->height] = malloc(newGrid->width*sizeof(*cells));
newGrid->cell_internal = cells;
for(unsigned x = 0; x < newGrid->width; x++) {
    for(unsigned y = 0; y < newGrid->height; y++) {
        cells[x][y] = ...;
    }
}

The downside of this approach is, that you will need to explicitly create an alias pointer for the cell_internal pointer in each function that works on the cell data with

Cell (*cells)[myGrid->height] = myGrid->cell_internal;

Probably, this is the better approach, as it would seem to be readable to more people.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
0

Use a flexible array. It's trivial to do with two malloc() calls, and possible to do with just one if you care to push the limits of alignment restrictions or strict aliasing or want to write the code to coerce the alignment of the portion of malloc()'d used to store Cell structures.

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell *cell[];
} Grid;

Grid *initGrid(unsigned width, unsigned height )
{
    // the Grid structure itself
    size_t bytesNeeded = sizeof( Grid );

    // space for pointers
    bytesNeeded += height * sizeof( Cell * );

    Grid *g = malloc( bytesNeeded );
    g->width = width;
    g->height = height;

    // get all the data needed with one malloc call
    g->cell[ 0 ] = malloc( width * height * sizeof( Cell ) );

    // fill in the pointers
    for ( unsigned ii = 1; ii < height; ii++ )
    {
        g->cell[ ii ] = g->cell[ 0 ] + ii * width;
    }

    return g;
}

void freeGrid( Grid *g )
{
    free( g->cell[ 0 ] );
    free( g );
}

If you don't mind pushing the limits of strict aliasing, you can do it with a flexible array and a single call to malloc() (it's left as an exercise for the reader to coerce the alignment of the data portion so that there's no potential alignment problems - that definitely is possible to do):

typedef struct Grid {
    unsigned width;
    unsigned height;
    Cell *cell[];
} Grid;

Grid *initGrid(unsigned width, unsigned height )
{
    // the Grid structure itself
    size_t bytesNeeded = sizeof( Grid );

    // space for pointers
    bytesNeeded += height * sizeof( Cell * );

    // space for data
    bytesNeeded += width * height * sizeof( Cell );

    Grid *g = malloc( bytesNeeded );
    g->width = width;
    g->height = height;

    // fill in the pointers
    // (technically a strict-aliasing/alignment violation as it assumes
    // that &(g->cell[ height ]) is suitable to store a Cell...)
    for ( unsigned ii = 0; ii < height; ii++ )
    {
        g->cell[ ii ] = ( Cell * ) &(g->cell[ height ]) +
            ii * width;
    }

    return g;
}
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56