arrayscstructflexible-array-member

how can i have a struct have an unknown size array member?


im trying to make a sort of minesweeper clone in c, but i cant figure out how to create a variable length array member of a struct and initialize it.

my struct definitions

typedef struct Cell
{
    Rectangle area;         // The area the cell covers
    bool hasMine;           // If the cell covers a mine
    bool covered;           // If the cell is currently covered
    int touching;           // How many mines the cell is touching
} Cell;

typedef struct Minefield
{
    int columns;        // The amount of columns
    int rows;           // The amount of rows
    int mines;          // The amount of mines
    Cell field[];       // An array of columns*rows Cells

} Minefield;

how im creating new instances of a Minefield

Minefield NewGame(int columns, int rows, int mines)
{
    int i = 0;

    Cell cells[columns*rows];

    for (i=0; i < columns*rows; i++) {
        int x = 0;
        int y = 0;

        if (i == 0) {
            Rectangle area = (Rectangle){0, 0, cellsize, cellsize};
            cells[i] = (Cell){area, false, true, 0};
        }
        else {
            Cell previous = cells[i-1];

            x = previous.area.x + cellsize;
            y = previous.area.y + cellsize;

            Rectangle area = (Rectangle){x, y, cellsize, cellsize};
            cells[i] = (Cell){area, false, true, 0};
        }
        
    }

    Minefield field = (Minefield){columns, rows, mines, cells};
    return field;
}

im new to c, ive done some googling and ive come across some very similar questions, but i cant really understand them or the examples they give. i think you can do what my errendous code is trying to do (or something very similar) with a gcc extension, but i want my code to be portable. how can i have a struct have an unknown size array member?


Solution

  • Yes, it's called flexible array member. It's usually used with dynamically allocated objects. However, you cannot return Minefield by value. The compiler will copy only sizeof(Minefield) data what is the size of object if length of array Minefield::field was 0.

    You can do:

    Minefield* NewGame(int columns, int rows, int mines)
    {
        Cell cells[columns*rows];
        ...
        // allocate memory for Minefield and cells array just after it
        Minefield* minefield = malloc(sizeof(*minefield) + sizeof cells);
        if (!minefield) { ... error handling ... }
    
        // set normal fields using compund literal
        *minefield = (Minefield){columns, rows, mines};
    
        // copy cells `array` to `minefield->field` array
        memcpy(minefield->field, cells, sizeof cells);
    
        return minefield;
    }
    

    The memcpy can be avoided by first allocating Minefield and writing directly to field member.

    Minefield* NewGame(int columns, int rows, int mines)
    {
        Minefield* minefield = malloc(sizeof(Minefield) + rows * columns * sizeof(Cell));
        if (!minefield) { ... error handling ... }
        *minefield = (Minefield){columns, rows, mines};
    
        Cell *cells = minefield->field;
        ...
        return minefield;
    }