0
snakeGame * createSnakeGame()
{
    int row = 10;
    int col = 10;

    snakeGame * game = (snakeGame *) malloc(sizeof(snakeGame));

    game->rows = row;
    game->columns = col;

    for (int i = 0; i < row; i++)
    {
        if (i == 0 || i == row - 1)
        {
            for (int j = 0; j < col; j++)
            {
                game->board[i][j] = BORDER;    
            }
        }
        else
        {
            game->board[i][0] = BORDER;
            game->board[i][col - 1] = BORDER;
        }
    }

    return game;
}
typedef struct 
{
    snakeEntity ** board;
    int rows;
    int columns;
} snakeGame;
typedef enum 
{
    SNAKE_HORIZONTAL,
    SNAKE_VERTICAL,
    SNAKE_UP_RIGHT,
    SNAKE_UP_LEFT,
    SNAKE_DOWN_RIGHT,
    SNAKE_DOWN_LEFT,
    FOOD,
    BORDER,
    EMPTY
} snakeEntity;
int main()
{
    snakeGame * game = createSnakeGame();
    printf("Success");
    return 0;
}

The print statement is not reached because the program hangs and abruptly exits after a random amount of time.

I have tried allocating the struct in different ways, such as accounting for the array size and initialising the board separately. However, the same UB remains.

// Adding the array size to the allocated memory
snakeGame * game = (snakeGame *) malloc(sizeof(snakeGame) + (row * col * sizeof(snakeEntity)));
// Allocating memory to each row individually to the size of the column
game->board = (snakeEntity **) malloc(row * sizeof(snakeEntity));
for (int i = 0; i < row; i++)
{
    game->board[i] = (snakeEntity *) malloc(col * sizeof(snakeEntity));
}
// Initialising the board separately
snakeGame * game = (snakeGame *) malloc(sizeof(snakeGame));
snakeEntity ** board = (snakeEntity **) malloc(row * col * sizeof(snakeEntity));
game->board = board;

I would appreciate some advice on how to solve the problem.

Blundergat
  • 37
  • 7
  • 1
    The `board` member should be looked at more as an array of pointers (to arrays). Not as a single array of `snakeEntity` elements. You need more loops to initialize each and every pointer. – Some programmer dude Apr 13 '23 at 11:27
  • @Someprogrammerdude Have I implemented what you suggested correctly in the newly added second example? The behaviour has not changed for me. – Blundergat Apr 13 '23 at 11:33
  • The code that is "[a]llocating memory to each row individually to the size of the column" is correct. – Some programmer dude Apr 13 '23 at 11:34
  • Oh! I see my silly mistake now. Thank you so much :) Feel free to post the answer yourself or I will add it myself later. – Blundergat Apr 13 '23 at 11:35
  • By the way, [in C you don't have to (and really shouldn't) cast the result of `malloc`](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc/605858). – Some programmer dude Apr 13 '23 at 11:36

1 Answers1

2

You don't allocate space for the board member before assigning to it in the loop: game->board[i][j].

As for snakeEntity ** board = (snakeEntity **) malloc(row * col * sizeof(snakeEntity));, this is wrong too. A board** is not a 2D array and cannot be used as one. It can however be used as a pointer to the first item in an array of pointers, to emulate a 2D array. In that case the correct usage would be:

game->board = malloc(row * sizeof(snakeEntity));
for(size_t i=0; i<row; i++)
{
  game->board[i] = malloc(col * sizeof(snakeEntity));
}

And then free() in the same manner.


However, the pointer to pointer version is naive (lots of bad books and bad teacher preach it). Check out Correctly allocating multi-dimensional arrays

There are several ways you could implement this using 2D arrays instead of pointer-to-pointer. One example is to use a flexible array member. Unfortunately flexible array members only support declared 1D arrays, but we can use one as place holder for a 2D array pointer. And then never actually access it as the 1D array it was declared as.

It gives very efficient code at the cost of readability. Example:

typedef struct 
{
  size_t rows;
  size_t cols;
  snakeEntity board[];
} snakeGame;

You'd then allocate everything in one go:

snakeGame* game = malloc (sizeof(snakeGame) + sizeof(snakeEntity[ROWS][COLS]));
game->rows = ROWS;
game->cols = COLS;
...
free(game); // and later free on a single line

board isn't initialized but now points at an allocated 2D array (filled with garbage).

We can fill it up by casting to an appropriate pointer type (this is the hard to read part):

snakeEntity (*board)[game->cols] = (snakeEntity(*)[game->cols]) game->board;

We can now use this pointer to fill up or otherwise access the 2D array, using 2D-array syntax:

for(size_t i=0; i<game->rows; i++)
{
  for(size_t j=0; j<game->cols; j++)
  {
    board[i][j] = whatever;
  }
}
Lundin
  • 195,001
  • 40
  • 254
  • 396