0

I need to read following text file:

2 2
Kauri tree
Waterfall
0 0 W S
0 1 E N

I wanted to use the scanf to get the first line and use the fgets for the second and the third line then use the scanf again for the rest of the lines.

I wrote the code like this:

#include <stdio.h>

#define NUM_OF_CHAR 2

int main()
{
    int node, edge;
    scanf("%d %d", &node, &edge);

    FILE* fp;
    fp = stdin;

    char* str[NUM_OF_CHAR];  //should be char str[NUM_OF_CHAR];

    for (int i = 0; i < node; i++) {
        fgets(str[i], 2, fp);     //should be fgets(str, 2, fp);
    }
    printf("%s", str[0]);         //printf("%s", str);
}

the input I entered was:

2 2
hello

I got Segmentation fault

I saw a similar question here, a person mentioned that I can call fgets once for getting the first line but ignore it and then use fgets again to get the second line. But I don't know how to do it.

gjjhhh_
  • 169
  • 3
  • 21
  • Use fgets() and then strtok() to split the string up. – Code Gorilla Aug 22 '16 at 11:30
  • As for your *current* problem, the crash you get, now think a little. You declare an array of pointers to `char`, but you never make these pointers actually *point* anywhere. – Some programmer dude Aug 22 '16 at 11:31
  • Hi Matt. Thanks for replying. I am new to c. I don't know how to use strtok(). I will look it up now. – gjjhhh_ Aug 22 '16 at 11:31
  • You also have another problem, and that is that the first line you get with `fgets` (after you fix the pointer problem) will be *empty*. That's because the previous `scanf` didn't read the newline at the end of the previous line so `fgets` will see it as an empty line. – Some programmer dude Aug 22 '16 at 11:32
  • Hi Joachim. Thanks. I thought I used them in the for loop. How can I fix it? – gjjhhh_ Aug 22 '16 at 11:33
  • Is it that after `scanf` read the first line, `fgets` will automatically read from the second line? – gjjhhh_ Aug 22 '16 at 11:36
  • Possible duplicate of [fgets doesn't work after scanf](http://stackoverflow.com/questions/5918079/fgets-doesnt-work-after-scanf) – n. m. could be an AI Aug 22 '16 at 11:44

3 Answers3

2

Local variables defined inside functions will, unless explicitly initialized, have an indeterminate value. For pointers that means they are pointing at a seemingly random location. Using any uninitialized variable, except to initialize it, leads to undefined behavior.

What happens here is that fgets will use the (uninitialized and seemingly random) pointer and use it to write into the memory it points to. This memory is in most cases not belonging to you or your program, and might even overwrite some other important data. This can lead to crashes, or other weird behavior or results.

The simplest solution is to make str an array of arrays of characters, like

#define NUM_OF_STRINGS 2
#define STRING_LENGTH 64
...
char str[NUM_OF_STRINGS][STRING_LENGTH];
...
fgets(str[i], sizeof str[i], stdin);

You need to make sure that STRING_LENGTH above is enough to fit every string including the newline and the string terminator. In the case of what I show above with it being 64 that means you can have lines of at most 62 characters.


Now as for the other problem I pointed out, with the first call to fgets reading an empty line.

If you have the input

2 2
hello

The input is stored in a buffer in memory, and then scanf and fgets reads from this buffer. The buffer will, with the above input, look something like this

+----+----+----+----+----+----+----+----+----+
|  2 |  2 | \n |  h |  e |  l |  l |  o | \n |
+----+----+----+----+----+----+----+----+----+

After the scanf call read the two numbers the input buffer looks like

+----+----+----+----+----+----+----+
| \n |  h |  e |  l |  l |  o | \n |
+----+----+----+----+----+----+----+

So what the very first call to fgets in the loop will see is the newline. So it reads that newline and then it's done, leaving the string "hello\n" in the buffer for the second call to fgets.

There are a few ways to solve this problem. The one I personally prefer is to use fgets universally to read lines, and if you need a simple parsing of the line, then use sscanf (note the leading s, also please see here for a good reference of all scanf variants) to do so.

Another way is to simply read characters from the input, one character at a time, and discarding them. When you read a newline, stop the loop and continue with the rest of the program.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
1

Consider the following example, where comments explain some important points:

#include <stdio.h>

#define NUM_OF_CHAR 2
#define LEN_OF_STR 20

int main()
{
    int node, edge;
    FILE* fp;
    fp = stdin;
    char strbuf[LEN_OF_STR];
    // stream is available after that
    // reading numbers
    fscanf(fp, "%d %d", &node, &edge);
    // reading strings
    for (int i = 0; i < node; i++) {
        // reading line from input stream
        fgets(strbuf, LEN_OF_STR, fp);
    }
    // cleaning input buffer
    while (getchar() != '\n');
    // reading lines with data
    char str[NUM_OF_CHAR];
    int a, b;
    for (int i = 0; i < node; i++) {
        // reading two numbers and two characters
        fscanf(fp, "%d %d %c %c", &a, &b, &str[0], &str[1]);
        // do something with dada, e.g. output
        printf("%d %d %c %c\n", a, b, str[0], str[1]);
    }
    return 0;
}

When you reading data with scanf or fscanf you can check results, e.g.:

    if (4 == fscanf(fp, "%d %d %c %c", &a, &b, &str[0], &str[1]))
    {
        // actions for correct data
    }
    else
    {
        // actions for wrong input
    }

here format line has 4 specifiers - "%d %d %c %c", so we check as "compare return value with 4"

VolAnd
  • 6,367
  • 3
  • 25
  • 43
1

I have solved my problem. I should not use a char* pointer and make it point to an array. The first parameter that is passed to the fgets function supposed to be a char* so I should have used an array only.

also, since scanf scanned off the first line already, if I use fgets next, it will automatically get the next line.

#include <stdio.h>

#define NUM_OF_CHAR 100

int main()
{
    int node, edge;
    scanf("%d %d", &node, &edge);

    FILE* fp;
    fp = stdin;

    char str[NUM_OF_CHAR] = {'\0'};

    for (int i = 0; i < node; i++) {
        fgets(str, NUM_OF_CHAR, fp);
    }
    printf("%s", str);
}
gjjhhh_
  • 169
  • 3
  • 21