1

I'm trying to save a board (which is a 2D array that has been dynamically allocated) into a file such that it can be later "loaded" by the user and they can use the board again. To save the board the user must input s filename. To get the user input, I have an else if statement for the save command:

else if (playerMove.command == 's') {
 char* fileName = NULL;
 scanf(" %s", fileName);
 implementSave(boardState, fileName);

Since we've only learned how to read/open/write to binary files in C, I tried to do the same for this in my implementSave function:

void implementSave(BoardState* boardState, char* fileName) {
 FILE* file = fopen(fileName, "wb");
 fwrite(fileName, sizeof(char), sizeof(boardState->board.theBoard), file);
 fclose(file);
}

Note: I use some structs in my program; the struct board contains char** theBoard, int numRows, int numCols, char blankSpace.

However, this gives me an error when I try to run the save command. Can anyone point me in the right direction?

asked Dec 15, 2017 at 17:08
9
  • 1
    What kind of error? If it is just not saving correctly, that is because the sizeof a char** is 4 or 8 (or something like that). You should compute the number of character cells to save or load directly: boardState->board.numRows*boardState->board.numCols. Commented Dec 15, 2017 at 17:12
  • char* fileName = NULL; scanf(" %s", fileName); -- you have allocated no storage for the fileName.... Also note that the %s directive automatically skips over leading whitespace, so no need for the leading space in the format string. Commented Dec 15, 2017 at 17:12
  • Depends on what is BoardState. But fwrite(fileName, sizeof(char), sizeof(boardState->board.theBoard), file); looks totally wrong anyway. Did you mean fwrite(boardState, sizeof(char), sizeof(boardState->board.theBoard), file);? Commented Dec 15, 2017 at 17:14
  • you won't be able to write out theBoard if it's a char ** as each element of theBoard is a pointer and it'd be meaningless - you'd need to write out each row separately Commented Dec 15, 2017 at 17:25
  • 1
    How/where is your 2D array declared? See How to create a Minimal, Complete, and Verifiable example. For a type ** (pointer to pointer to type), you should write the total number of values as the first value in the file (and perhaps the size -- char, short, int.. before that), then you will have to call fwrite once for each pointer to write the value. Depending on your declaration, there is no guarantee that the values are contiguous in memory. Commented Dec 15, 2017 at 18:43

2 Answers 2

1

This code

 fwrite(fileName, sizeof(char), sizeof(boardState->board.theBoard), file);

means:

Write data to the file file. The data is found at the memory location fileName points to, the data consists out of sizeof(boardState->board.theBoard) items and every single item is sizeof(char) bytes big.

Does that sound right to you? I don't think so; it's already wrong that the data can be found at the position fileName points to as you want to write the board data to the file and no the file name, right?

Also be careful with sizeof(); sizeof() cannot dynamically determine the content of some memory, it only knows the size of static memory. E.g.

char test[20];
size_t s = sizeof(test);

s will be 20. But now consider this:

char test[20];
char * ptr = test;
size_t s = sizeof(ptr);

Now s will be 4 or 8 as pointers are usually 4 bytes in size or 8 bytes in size. sizeof() gives you the size of the pointer here, not the size of the memory the pointer points to. There is no way in C to obtain the size of a memory block a pointer points to, this size must always be known.

You say your board is char ** theBoard, so theBoard is a pointer to an array of pointers to memory or characters.

theBoard -> [0] -> ['a1', 'b1', 'c1', 'd1', ... ] 
 [1] -> ['a2', 'b2', 'c2', 'd2', ... ] 
 [2] -> ['a3', 'b3', 'c3', 'd3', ... ]
 :

In that case the creation code would need to look like that (assuming that [0], [1], ... are the rows):

 theBoard = calloc(numberOfRows, sizeof(char *));
 for (size_t i = 0; i < numberOfRows; i++) {
 theBoard[i] = calloc(numberOfCols, sizeof(char));
 }

If that is the case, you need to write your data row by row:

for (size_t row = 0; row < numberOfRows; row++) {
 fwrite(boardState->board.theBoard[row], sizeof(char), numberOfCols, file);
}

Of course, assuming that all rows have have an equal number of cols.

If I can give you a tip, don't make the board char **, just make it char *, as it makes everything so much easier. See, if your board is 20x30 (20 rows, 30 cols), then you can define your board like this:

char * theBoard = calloc(numberOfRows * numberOfCols, sizeof(char))

Now you just have a single array, like this:

theBoard -> ['a1', 'b1', 'c1', 'd1', ... ,
 'a2', 'b2', 'c2', 'd2', ... ,
 'a3', 'b3', 'c3', 'd3', ... ,
 :
 ]

How would you access a specific field? Very simple:

 int row = 5;
 int col = 8;
 char field = theBoard[(row * numberOfCols) + col];

And then you can write the entire board in one call:

fwrite(theBoard, sizeof(char), numberOfRows * numberOfCols, file);

See, much easier. Also you can just free the entire board by calling free(theBoard); whereas when using the char ** approach, you have to do this instead:

 for (size_t i = 0; i < numberOfRows; i++) {
 free(theBoard[i]);
 }
 free(theBoard);
answered Dec 15, 2017 at 19:03
Sign up to request clarification or add additional context in comments.

Comments

0

You can not just write the double-pointer to file. You have to index each element in the first pointer and write the data out in chunks of rows (or columns, but I've demonstrated rows). To do this use a for loop. Note that (assuming the board is all you are saving to the binary file) the resulting file size should be numRow * numCol bytes.

int i;
for (i = 0; i < BoardState->board.numRow; ++i) {
 fwrite(BoardState->board.theBoard[i], sizeof(char), BoardState->board.numCol, file)
}

Reading ("loading") the file is done in reverse of the code above - note that it is unwise to only save the board to file - I suggest at the start of the file you save the number of rows and columns to make allocation of memory easy while reading (loading) the file.

So assuming you store the number of rows/columns in an int the above code then becomes

fwrite(&BoardState->board.numRow, sizeof(int), 1, file);
fwrite(&BoardState->board.numCol, sizeof(int), 1, file);
int i;
for (i = 0; i < BoardState->board.numRow; ++i) {
 fwrite(BoardState->board.theBoard, sizeof(char), BoardState->board.numCol, file)
}

NOTE this has the possibility of introducing endianness problems. You can Google that if your interested.

Dúthomhas
10.8k2 gold badges21 silver badges48 bronze badges
answered Dec 15, 2017 at 18:05

3 Comments

I will upvote your answer if you fix the calls to fwrite() for correct argument usage.
@Dúthomhas didn't catch that! Thanks - hard to answer/edit on mobile.
Yeah, I’ve learned it isn’t worth my effort unless I am seated in front of my PC.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.