pawel.ad
pawel.ad

Reputation: 691

Error handling while reading file in C

I'm finishing my C implementation of Conway's Game of Life, and I have a problem with error handling when parsing incorrect netting file.

File format is:

rows cols
x1   y1
x2   y2

Reading function:

net_t *file_to_net( net_t *n, char *filename )
{
assert( n != NULL );

FILE *f = fopen( filename, "r" );
if( f == NULL )
    print_error_file( filename );

if( fscanf( f, "%d %d", &(n->rows), &(n->cols) ) != 2 )
    print_error("net");

n->vec = calloc( n->rows * n->cols, sizeof(unsigned char) );
if( n->vec == NULL )
    print_error("alloc");

int i, x, y;
while( fscanf( f, "%d %d", &x, &y ) == 2 )
{
    if( x > n->rows || y > n->cols )
    {
        fprintf( stderr, "%sERROR:%s Coordinates of living cell in net file is bigger then net dimensions.\n", COLOR_RED, COLOR_RESET );
        exit(EXIT_FAILURE);
    }

    i = ( n->cols * (x - 1) ) + (y - 1);
    n->vec[i] = 1;
}
if( !feof(f) )
    print_error("net");

fclose(f);

return n;
}

Error handling works when there are letters instead of digits (while loop ends, but not EOF), but it doesn't work when I don't have one of the coordinates (there is an odd number of numbers), for example:

50 50
2 3
3 4
4
4 3
4 4

does not end with error. I've been trying to implement it, but I can't make it work in all 3 cases (good file, non-digits inside and odd number of numbers).


EDIT

OK, thanks very much for the help. Ended with this code of the loop, which covers all three cases:

int i, x, y;
int pos;
while( fgets( line, MAXL, f ) != NULL )
{
    if( sscanf( line, "%d %d %n", &x, &y, &pos ) != 2 )
    {
        print_error("net");
    }
    else if( line[pos] != '\0' )
    {
        print_error("net");
    }


    if( x > n->rows || y > n->cols )
    {
        fprintf( stderr, "%sERROR:%s Coordinates of living cell in net file is bigger then net dimensions.\n", COLOR_RED, COLOR_RESET );
        exit(EXIT_FAILURE);
    }

    i = ( n->cols * (x - 1) ) + (y - 1);
    n->vec[i] = 1;
}

Upvotes: 0

Views: 2562

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 753705

You can replace:

while( fscanf( f, "%d %d", &x, &y ) == 2 )

with:

int rc;

while ((rc = fscanf(f, "%d %d", &x, &y)) == 2)
{
    …
}

if (rc == EOF)
    …report EOF…
else
    …report format error…

The else clause can be triggered because of non-numeric data in the file, or because there was a number missing somewhere. Note that scanf() won't care if there are 50 space-separated numbers on a single line, nor if there are 20 blank lines between each separate coordinate, or any hybrid mess of a layout.

If you want to read by lines and report, you probably want to use some of the ideas from the answer by user3386109, plus some extra tweaks:

char line[4096];
int rc;
int pos;

while (fgets(line, sizeof(line), stdin) != NULL)
{
    if ((rc = sscanf(line, "%d %d %n", &x, &y, &pos)) != 2)
    {
        …analyze for EOF or format error…
    }
    else if (line[pos] != '\0')
    {
        …trailing non-blank junk on line…
    }
    else
    {
        …all clean and tidy…
    }
}

Note that %n conversion specifications are not counted in the results (hence the check is still 2, not 3). Also, the space before the %n is OK in sscanf(), but would be horrendous for interactive input (the input would not end until the user typed some non-white space character). These sorts of subtle details are why the scanf() family of functions are something of a nightmare for beginners (heck — they don't make it easy for experienced programmers either).

Upvotes: 1

user3386109
user3386109

Reputation: 34829

As was mentioned in the comments, fscanf treats newline characters as if they were spaces, so it has no concept of lines. If you put one number on a line, fscanf will read that number, and then get the second number from the next line. An easy solution to this problem is to use fgets to read lines from the file and use sscanf to extract the numbers from each line. That doesn't detect all errors, (for example it won't detect three numbers on a line), but it's a little better than what you have. For stringent error checking, you could read lines with fgets, and then break the line into tokens with strtok and then verify that each token is a valid number by writing your own string-to-number conversion algorithm. The built-in conversion routines are notoriously bad at error checking.

Here's a snippet that demonstrates how to use fgets with sscanf

#define MAXL 1024
static char line[MAXL];

    while ( fgets( line, MAXL, stdin ) != NULL )
    {
        if ( sscanf( line, "%d %d", &x, &y ) != 2 )
        {
            fprintf( ... );
            exit( EXIT_FAILURE );
        }

        ...

    }

Upvotes: 1

Related Questions