问题
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