0

I'm currently working on a Tic Tac Toe game in C and have run into a bit of a snag. I've defined a struct to represent the game board (see below, "board.h") and am trying to figure out to define a sort of "constructor" function that will initialize a default version of a Board struct dynamically and return a pointer to it. More specifically, I'm having difficulty determining exactly how I should use the malloc function to dynamically allocate the space for the prows field of the Board struct, regarding both the input to the malloc function and how to cast the result of the malloc function from type "void pointer" - void * - to type "pointer to array of int pointers."

In the code box below, I've included the contents of both my "board.h" and "board.c" files: board.h contains my definition of the Board struct and the prototypes for the functions implemented in board.c . In board.c, I've already tried implementing the initBoard function to initialize a Board struct on heap and return a pointer to it, however I'm unsure if my handling of the initialization of the prows field is correct (I've included my step by step reasoning in the initBoard function implementation). I was hoping someone could help me understand how to use malloc to initialize the prows field and why it must be done that way.

/***
 * =======================================
 *                board.h
 * =======================================
***/

#ifndef BOARD_H_INCLUDED
#define BOARD_H_INCLUDED

const unsigned int boardDim = 3;

typedef struct {
    int * (*prows)[boardDim];
    int score;
    unsigned int turn;
    _Bool isTerminal;
} Board;

Board * initBoard(void);

#endif



/***
 * =======================================
 *                board.c
 * =======================================
***/

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

#include "board.h"

/*
 * to initialize the prows field of a new Board struct, I figured it would be a good
 * approach to first initialize the array of int pointers to which prows points to and 
 * then initialize prows by taking the address of the initialized array of int pointers
*/
Board * initBoard(void) {
    /*
     * initialize array of int pointers called "rows" ...
     *
     * from what I know, rows stores the address of the first element in the array's
     * memory block, so a pointer to the "rows" array will essentially be a pointer to an
     * int pointer, so we should cast the returned void pointer (void *) from malloc to 
     * type pointer to int pointer (int **) ...
     * 
     * lastly, because rows will contain (boardDim) number of pointers (one for each row
     * in the board), inside malloc, I should pass "boardDim * sizeof(int *)" to allocate
     * (boardDim) number of int pointers
    */
    int * rows[boardDim] = (int **) malloc(boardDim * sizeof(int *));
    
    int * row;
    for (row = rows; row < rows + boardDim; row++) {
        row = (int *) malloc(sizeof(int));
    }

    int * (*prows)[boardDim] = &rows;

    int score = 0, turn = 0;
    _Bool isTerminal = 0;

    Board * board = (Board *) malloc(sizeof(Board));

    board->prows = prows;
    board->score = score;
    board->turn = turn;
    board->isTerminal = isTerminal;

    return board;
}
Charlie
  • 33
  • 1
  • 9
  • 2
    Before I post an answer to this, why is `Board` member `prows` a pointer to a pointer-to-array [boardDim] of `int` ? In other words, is there some reason that member is not simply `int (*prows)[boardDim]` ? Also, always consider it a warning: whenever you find yourself saying "so we should cast" in C there is a high probability you're doing something wrong, *especially* when first learning the language. Later on there are times when it is unavoidable (socket code, for example), but not in this case. If you find yourself doing it, it's probably a code or design smell. – WhozCraig Aug 30 '22 at 16:01
  • @WhozCraig I'm not using a pointer to an array because I was hoping to replicate a 2D array using pointers. My thought was that an array of int pointers could store the starting addresses of each row in memory, and a pointer to this array would make the struct more efficient. Regarding the casting, when I was learning how to use dynamic memory allocation functions like malloc, calloc, realloc, and free, I was taught to cast the return types of malloc, calloc, and realloc since they return void pointers. – Charlie Aug 30 '22 at 16:23
  • 2
    I agree with @WhozCraig. There's an extra level of indirection. If it's tic tac toe and the board is guaranteed to be 3x3, then you can just have int prows[boardDim][boardDim] and not use malloc at all. If this is the start of something larger, I agree with his answer. Also, dont cast malloc return types in C, but do cast the return type if compiling for C++. For example, https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – user1801359 Aug 30 '22 at 16:33
  • 1
    And I concur with @user1801359 . unless there is some unstated desired to runtime-expand a board to something *other* than 3x3, the dynamic management of `prows` is pointless; a simple `int rows[boardDim][boardDim];` member would suffice, and a single `Board *board = malloc(sizeof *board);` would allocate everything you need in one shot. If there *is* a goal of a dynamic board dimension, then (a) that dimension should be stored as part of `Board`, and (b) that introduces a need for dynamic allocation of the grid, but even then, a single-allocation with creative row-major indexing would work. – WhozCraig Aug 30 '22 at 16:44
  • @WhozCraig @user1801359 thank you for your advice, I found it very helpful. I guess I got too caught up in pointers and efficiency to see a more obvious and much simpler solution. For some reason, the class I took to learn C taught us that we needed to cast return values from malloc, calloc, and realloc, as well as the input pointer to free. I never knew that `void *` would be automatically upgraded to the proper type of pointer... kinda makes you wonder about the quality of the class lol – Charlie Aug 30 '22 at 17:22
  • Also, members of `struct` and `union` types such as `Board` are not allowed to have *variably modified* types. The `prows` member has a *variable length array* type which is a variably modified type. It has a variable length array type because `boardDim` is not an *integer constant expression*. You could change `boardDim` to be an integer constant expression by declaring it as an enumeration constant, for example by replacing `const int boardDim = 3;` with `enum { boardDim = 3 };`. – Ian Abbott Aug 30 '22 at 17:22
  • 1
    @IanAbbott why so complicated? `#define boardDim 3` is 100% enough in this case – 0___________ Aug 30 '22 at 18:34

1 Answers1

1

Your program is overly complicated - you should take some time to rethink your approach and plan everything out.

Nevertheless here is how to allocate what you desire:

#define boardDim 3

int *(*prows)[boardDim];
prows = malloc(sizeof(int *[boardDim]));
  • Thank you for your answer, it makes much more sense now that I know I shouldn't be casting the retval of malloc. I'm learning C from a more OOP-centered background, so I try to replicate the paradigm in C. That being said, I know C isn't really an OOP language, so would you have any advice as to how I should approach planning out my program? The only other option I feel like I can choose is having a bunch of global variables, but I know that isn't really good practice. – Charlie Aug 31 '22 at 12:46
  • 1
    Your program can be broken down into various blocks. Focus on one block at a time. For each block, implement a function/ a set of functions that focus on various aspects of that block. When these functions come together you will notice that you have solved a part of the puzzle. Keep doing this and you should have a solid program. One small thing to keep in mind is that you don't need to make the program super efficient and optimized at the start. First, you should focus on building something. Then at the end you can evaluate the approach you took and start optimizing from there. – professional pro Aug 31 '22 at 14:51
  • 1
    Thank you for that insight. I'm assuming that's what's meant by the term "modularity," I just didn't really understand if there was an expected manner of using it. I'll definitely follow it going forward – Charlie Aug 31 '22 at 16:13