Populating one char array using fscanf changes the value of another char array

↘锁芯ラ 提交于 2019-12-12 19:04:54

问题


I'm first using fscanf to populate my first array then again using fscanf from the same input file to populate another array. However this seems to be shifting the values in my first array.

Here is my input:

4 
abcd
efgh
ijkl
mnop
qrst
uvwx
yz12
3456

Here is my code:

#include <stdio.h>

void prints( int n, char sqr[n][n]){
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
            printf("%c", sqr[i][j]);
        }
        printf("\n");
    }
}

int main(void){

    FILE *in = fopen("transform.in", "r");
    FILE *out = fopen("transform.out", "w");

    int num;
    fscanf(in, "%d", &num);

    char square[num][num];
    for (int i = 0; i < num; i++){
        fscanf(in, "%s", square[i]);
    }

    prints(num, square);
    printf("\n");

    char endSquare[num][num];
    for (int i = 0; i < num; i++){
        fscanf(in, "%s", endSquare[i]);
    }

    fclose(in);

    prints(num, square);
    printf("\n");

    prints(num, endSquare);
    printf("\n");

    fclose(out);

    return 0;
}

And here is the output I get:

abcd
efgh
ijkl
mnop

bcd
efgh
ijkl
mnop

qrst
uvwx
yz12
3456

As you can see my square array seems to be changed after I populate endSquare.


回答1:


In addition to not accounting for the nul-terminating character that the %s format specifier will append, you are making things extremely hard on yourself by attempting to read lines of data with a formatted input function fscanf. When doing line-oriented input, it is far better to use a line-oriented input function such as fgets and then parse the needed information from the buffer containing the entire line of information. Why? Mixing numeric and character input with the scanf family of functions can pose many traps for those who do not account for all characters that remain in the input buffer or account for how the differing fscanf format specifiers handle leading whitespace.

Particularly, in your case when reading num, you have no way to limit the number of characters read by fscanf using the %s format specifier. You cannot include a variable field width to protect against writing beyond your array bounds. (e.g. you can't use something like %nums for %4s to insure you limit the characters read to 4) When using a VLA to hold a specific number of characters based on what is read from the first line, there just isn't an elegant way to incorporate num and validate/limit the number of characters read using fscanf.

All of this adds up to a train-wreck waiting to happen if there happens to be a stray space (or other character) at the end of one of your lines.

So, how to handle reading only 4-char into each row of square and endsquare? (note: the capital 'S' was reduced to lower-case to match normal C-style) When you need to handle lines of input, use a line oriented input function and provide a buffer sufficient to handle each line of data. I would rather use a 128-char buffer and insure I read every 4-5 char line than accidentally read a 5-char line into a 4-char buffer -- to avoid Undefined Behavior. Moreover, you can use the same buffer to read every line of data.

Next, you must validate every read and every conversion to insure you do not process garbage from the point of the failed read or failed conversion forward in your code. For example, when reading your data file, you can declare a simple buffer and read your first line as follows:

#define MAX 128
...
    char buf[MAX] = "";
    ...
    if (!fgets (buf, MAX, in)) {    /* read 1st line with 'num' */
        fprintf (stderr, "error: read of 'num' failed.\n");
        return 1;
    }

    errno = 0;          /* errno to check after strtol conversion */
    int num = (int)strtol (buf, NULL, 10);  /* convert num to int */
    if (errno) {        /* validate */
        fprintf (stderr, "error: failed conversion of 'num'.\n");
        return 1;
    }

When reading each subsequent line, you need to confirm that the entire-line was read by checking for the trailing '\n' (read and included by fgets) and if it isn't present, your line is too long (handle error). You also need to know if there are num characters in the line since you are NOT storing the lines as strings (only as a character array). If there are not num-chars read, you cannot copy num-chars to square[i] or endsquare[i]. Since you are using a for loop, you also must check that each line you read is a valid line. Just because you read 4 as line one, there is no guarantee there are 8 lines in the file. (you would ideally want to use a while (fgets (buf, MAX, in)) loop to drive the rest of the input and a counter to break after 4-lines are read, but you can protect within the for loop by validating with an if (fgets (buf, MAX, in)) as well.

With that in mind, you could fill the character arrays with exactly 4-chars read from the file (or less) while checking for any unexpected empty lines with something like the following while still using your for loop:

    char square[num][num];
    for (int i = 0; i < num; i++) {
        size_t len, n = num;
        if (!fgets (buf, MAX, in))      /* read line int buf */
            break;                      /* break if no line read */
        len = strlen (buf);             /* get length */
        if (buf[len - 1] != '\n') {     /* if no '\n' at end, too long */
            fprintf (stderr, "error: line[%d] too long.\n", i);
            return 1;
        }
        if (*buf == '\n') {             /* 1st char is '\n' - empty */
            fprintf (stderr, "error: empty line encountered.\n");
            return 1;
        }
        if ((int)(len - 1) < num)       /* if less than num, reduce num */
            n = len - 1;
        memcpy (square[i], buf, n);     /* copy 'num' chars from buf */
    }

You can do the same for your endsquare loop. Putting it all together, you could do something like the following (note: out is unused, so the code related to it is commented out below):

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

#define MAX 128

void prints (int n, char (*sqr)[n])
{
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            putchar (sqr[i][j]);
        }
        putchar ('\n');
    }
}

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

    char buf[MAX] = "";
    FILE *in = fopen (argc > 1 ? argv[1] : "transform.in", "r");
    // FILE *out = fopen ("transform.out", "w");

    if (!in /* || !out */) {  /* validate both files open */
        fprintf (stderr, "error: file open failed.\n");
        return 1;
    }

    if (!(fgets (buf, MAX, in))) {  /* read 1st line with 'num' */
        fprintf (stderr, "error: read of 'num' failed.\n");
        return 1;
    }

    errno = 0;          /* errno to check after strtol conversion */
    int num = (int)strtol (buf, NULL, 10);  /* convert num to int */
    if (errno) {        /* validate */
        fprintf (stderr, "error: failed conversion of 'num'.\n");
        return 1;
    }

    char square[num][num];
    for (int i = 0; i < num; i++) {
        size_t len, n = num;
        if (!fgets (buf, MAX, in))      /* read line int buf */
            break;                      /* break if no line read */
        len = strlen (buf);             /* get length */
        if (buf[len - 1] != '\n') {     /* if no '\n' at end, too long */
            fprintf (stderr, "error: line[%d] too long.\n", i);
            return 1;
        }
        if (*buf == '\n') {             /* 1st char is '\n' - empty */
            fprintf (stderr, "error: empty line encountered.\n");
            return 1;
        }
        if ((int)(len - 1) < num)       /* if less than num, reduce num */
            n = len - 1;
        memcpy (square[i], buf, n);     /* copy 'num' chars from buf */
    }

    prints (num, square);
    putchar ('\n');

    char endsquare[num][num];
    for (int i = 0; i < num; i++) {
        size_t len, n = num;
        if (!fgets (buf, MAX, in))      /* read line int buf */
            break;                      /* break if no line read */
        len = strlen (buf);             /* get length */
        if (buf[len - 1] != '\n') {     /* if no '\n' at end, too long */
            fprintf (stderr, "error: line[%d] too long.\n", i);
            return 1;
        }
        if (*buf == '\n') {             /* 1st char is '\n' - empty */
            fprintf (stderr, "error: empty line encountered.\n");
            return 1;
        }
        if ((int)(len - 1) < num)       /* if less than num, reduce num */
            n = len - 1;
        memcpy (endsquare[i], buf, n);  /* copy 'num' chars from buf */
    }

    fclose(in);

    prints (num, square);
    putchar ('\n');

    prints (num, endsquare);
    putchar ('\n');

    // fclose(out);

    return 0;
}

Note: never use the variadic printf function to output a single-character, instead, use a function designed to output a single character like putchar (or fputc). And also note, you should preserve the count of individual lines and pass the correct number to prints in the event there are less than num lines read in any group (that is left for you).

Example Input

$ cat dat/list.txt
4
abcd
efgh
ijkl
mnop
qrst
uvwx
yz12
3456

Example Use/Output

$ ./bin/inout dat/list.txt
abcd
efgh
ijkl
mnop

abcd
efgh
ijkl
mnop

qrst
uvwx
yz12
3456

There are always more validations you can do, but at a minimum something like the above will work for your data with reasonable error checking. Look things over and let me know if you have any questions.

If you want to print squares, you could always do something like the following:

void prints (int n, char (*sqr)[n])
{
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (j)
                putchar (' ');
            putchar (sqr[i][j]);
        }
        putchar ('\n');
    }
}

Example w/Modified prints

$ ./bin/inout dat/list.txt
a b c d
e f g h
i j k l
m n o p

a b c d
e f g h
i j k l
m n o p

q r s t
u v w x
y z 1 2
3 4 5 6


来源:https://stackoverflow.com/questions/43964879/populating-one-char-array-using-fscanf-changes-the-value-of-another-char-array

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!