0

I am trying to save a 2d array of characters to a file. My 2d array is a board where I can draw, resize, delete and add row/col.

I am facing a problem in saving it to a file.

First off, I don't know which of these two function is the faulty one. Is there a way to look up the .txt file that I'm trying saving it to and see if it worked?

Second, here are the two function, SAVE & LOAD to .txt file.

the name of the .txt file is passed on the parameter from the user. Please do ignore the "itreachedhere" print commands.

void save_board(char* textfile, board* theboard){
    FILE* outfile = NULL;
    int i,j;

    outfile=fopen(textfile, "w");
    if(outfile==NULL){
        printf("Could not open file %s.\n", textfile);
    }
    printf("itreachedhere\n");
    for(i=0;i<theboard->num_rows;i++){
        for(j=0;j<theboard->num_cols;j++){
            fprintf(outfile, " %c ", theboard->myboard[i][j]);
        }
        fprintf(outfile, "\n");
    }
    fclose(outfile);
}

void load_board(char* textfile, board* theboard){
    FILE* infile = NULL;
    int i, num_chars=0;
    char current_char;
    int lengths[10000];
    int index=0;
    int row=0;

    infile=fopen(textfile, "r");

    if(infile==NULL){
         printf("Could not open file %s.\n", textfile);
        // exit(1);
    }

    while(!feof(infile)){
        current_char=fgetc(infile);
        num_chars++;
        //printf("%d ", num_chars);
        if(current_char=='\n'){

             // This array stores the length of characters of each line
            lengths[index]=num_chars+1;
            //update index
            index++;
            // Increment the number of row read so far
            row++;
            // Reset the number of characters for next iteration
            num_chars=0;

        }
    }

    //printf("%d",lengths[1]);
    theboard->num_rows=row;
    theboard->num_cols=lengths[0];

    //recreate the board
    createboard(theboard);



    //now we need to copy the characters in infile to myboard
    for(i=0;i<theboard->num_rows;i++){
        fgets(theboard->myboard[i],(lengths[i]+1),infile);
        //printf("%c", theboard->myboard[i][0]);
    }
    printf("itreachedhere\n");
    printboard(theboard);
    //printf("itreachedhere\n");
    fclose(infile);


}

so, for example. If I have a board of size 4 x 5

3  *  *  *  *  * 
2  *  *  *  *  * 
1  *  *  *  *  * 
0  *  *  *  *  * 
   0  1  2  3  4  

I would then save it like so,

Enter your command: s e.txt

and load it like so,

Enter your command: l e.txt

then I would get something like this when I try to print out the board

3  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  � 
2  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  � 
1  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  � 
0  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  � 
   0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16

there is nothing wrong with my printboard function. I think my fault is I don't know how to save the file correctly or retrieve the file correctly.

thanks for the comment. now i'm getting this

2     *        *        *        *     

1     *        *        *        *     

0     *        *        *        *     

   0  1  2  3  4  5  6  7  8  9  10  11  12  13 
Q Mallow
  • 11
  • 5
  • well you should not continue with your execution of save_board if your outfile is NULL, it should return within that if statement to prevent null pointer exceptions from occurring. same goes for the load function. – RAZ_Muh_Taz Dec 07 '16 at 01:17
  • Your `infile` file pointer is at the end when the `while` loop ends. When the `for` loop executes, there is nothing left for `fgets()` to read from `infile`. – alvits Dec 07 '16 at 02:36
  • You can just open .txt files to see the contents and see if your code works. alvits is right, you've read the file to get the number of lines and line lengths but left the file pointer at the end so you cannot read the data again where you have "now we need to copy the characters". you can try rewinding the file pointer before that and see what you get! – john elemans Dec 07 '16 at 03:00
  • that makes a lot of sense. I added fseek() and it was able to copy the thing but then i'm getting this (edit above)^. When I print out the length of the col //printf("%d",lengths[1]); it does give me a very large number and it adds up every time i try to save and load. WHY? – Q Mallow Dec 07 '16 at 05:02
  • You will want to look at [**Why is while ( !feof (file) ) always wrong?**](http://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong). Your issue is similar. After you read the last character in the file (a `'\n'` if a proper POSIX end-of-line is present, or a regular char if not) no error condition is yet set on the stream. So you loop again, call `fgetc` which sets the error condition on your stream, but you are already in the loop, so you `num_chars++;` even though no character was read and then on your next iteration exit -- one too late. – David C. Rankin Dec 07 '16 at 08:56
  • I don't get what you're saying :( – Q Mallow Dec 07 '16 at 21:33
  • If `board::myboard` is a static 2D array like `myboard[rows][columns]` then size of myboard will be static. Therefore you can simply write the whole array directly `fwrite(theboard->myboard, sizeof(theboard->myboard), 1, outfile);` Similarly read the whole array directly. Assuming you are separately saving the `rows` and `columns` also. – sameerkn Dec 08 '16 at 07:11

1 Answers1

1

Continuing from the comments, I'll focus on your read. Re-read the line about feof being wrong. The short version in your case is the stream error condition (which is what feof looks for) is not set until you call fgetc(infile) (at which point you are already inside your loop and you have updated num_chars even though you read EOF on your last trip through the loop).

Now lets talk about why your code would fail anyway, even if you were correctly using EOF. When you declare char current_char; and then attempt a correct test of while ((current_char = fgetc (infile)) != EOF) {...}, you would never see EOF set comparing a char against an int. To correct this problem, your current_char must be declared as int.

Other issues with your read. There is no reason to store the number of characters per-line in the lengths array (unless you have a jagged array). In a normal square/rectangular board representation, all rows will have an equal number of elements. What you need to know is the number of characters in each row and you need to validate your read against that fixed number per-row.

In addition, what if you are reading a board file that doesn't include a '\n' at the end of the last line (called a non-POSIX end-of-line/end-of-file). You can protect against this condition by a simple check of whether the last character read was in fact a '\n' or not. If it wasn't, then you need to increment your row-index by 1 or you will be missing data. (which means you need to save the last char read for the comparison)

Below is a short example of one approach to reading an unknown number of rows/cols up to a fixed length into your lengths array. I use fp for your infile and rows x cols and ncol for the indexes and per-row character comparison. I also declare a constant NDATA instead of using a magic number like 10000 in the code. (that makes adjusting the size a simple change) The code simply reads from the filename given as the first argument to the program (or from stdin by default if no filename is given) Give the following a try:

#include <stdio.h>

enum { NDATA = 1000 };

int main (int argc, char **argv) {

    char lengths[NDATA] = "", last = 0; /* initialize all variables */
    int current_char = 0,   /* must be int to indicate EOF (-1) */
        rows = 0,           /* row index */
        cols = 0,           /* col index */
        ncol = 0,           /* num cols per-row, must be equal for each row */
        ndx = 0;            /* lengths index */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open */
        fprintf (stderr, "fileio() error: file open failed '%s'\n", argv[1]);
        return 0;
    }

    while ((current_char = fgetc (fp)) != EOF) { /* read each char until EOF */

        if (current_char == '\n') { /* if end of line encountered on read */

            if (!ncol)                  /* is ncols set?, if not */
                ncol = cols;            /* set it */
            else if (cols != ncol) {    /* otherwise, if not correct - error */
                fprintf (stderr, "error: unequal number of cols at row '%d'\n",
                        rows);
                return 1;
            }
            cols = 0;   /* reset column index  */
            rows++;     /* increment row index */
        }
        else {  /* normal char, add to lengths, increment index */
            lengths[ndx++] = current_char;
            cols++;
        }
        last = current_char;
        if (ndx == NDATA) { /* verify you don't exceed storage */
            fprintf (stderr, "warning: lengths array is full.\n");
            break;
        }
    }
    if (fp != stdin) fclose (fp);   /* close file */

    /* protect against non-POSIX end-of-line on last line in file */
    if (last != '\n') {
        if (!ncol)                  /* is ncols set?, if not */
            ncol = cols;            /* set it */
        else if (cols != ncol) {    /* otherwise, if not correct - error */
            fprintf (stderr, "error: unequal number of cols at row *'%d'\n",
                    rows);
            return 1;
        }
        cols = 0;   /* reset column index  */
        rows++;     /* increment row index */
    }

    printf ("board: %d x %d\n\n", rows, ncol);
    for (int i = 0; i < ndx; i++) {
        if (i && i % ncol == 0)
            putchar ('\n');
        printf (" %c", lengths[i]);
    }
    putchar ('\n');

    return 0;
}

Example Board Input File

$ cat dat/board.txt
01234
56789
abcde
fghij

Example Use/Output

$ ./bin/boardread <dat/board_neol.txt
board: 4 x 5

 0 1 2 3 4
 5 6 7 8 9
 a b c d e
 f g h i j

Look over how the read is performed, and how the validations are done. When you are done looking at the character-oriented read with fgetc, think about how much simpler the whole read could be using line-oriented input with fgets to read each line, then followed by a simple call to strlen to validate the length (taking into account the '\n' include by fgets in the read. Let me know if you have any questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85