fgets doesn't work after scanf [duplicate]
Asked Answered
L

7

22
#include <stdio.h>
#include <string.h>
#include <ctype.h>

void delspace(char *str);

int main() {
    int i, loops;
    char s1[101], s2[101];

    scanf("%d", &loops);

    while (loops--) {
        fgets(s1, 101, stdin);
        fgets(s2, 101, stdin);
        s1[strlen(s1)] = '\0';
        s2[strlen(s2)] = '\0';

        if (s1[0] == '\n' && s2[0] == '\n') {
            printf("YES\n");
            continue;
        }

        delspace(s1);
        delspace(s2);

        for (i = 0; s1[i] != '\0'; i++)
            s1[i] = tolower(s1[i]);

        for (i = 0; s2[i] != '\0'; i++)
            s2[i] = tolower(s2[i]);

        if (strcmp(s1, s2) == 0) {
            printf("YES\n");
        }
        else {
            printf("NO\n");
        }
    }

    return 0;
}

void delspace(char* str) {
    int i = 0;
    int j = 0;
    char sTmp[strlen(str)];

    while (str[i++] != '\0') {
        if (str[i] != ' ') {
            sTmp[j++] = str[i];
        }
    }
    sTmp[j] = '\0';
    strcpy(str, sTmp);
}

After I entered "loops", "s1" was assigned a blank line automatically. How does it happen? I'm sure my keyboard works fine.

Lamentation answered 6/5, 2011 at 23:37 Comment(5)
"I'm sure my keyboard works fine." Lol!Kreindler
Some variant of this question seems to be asked here every day or so.Jacobina
The function delspace is definitely wrong! This will convert abc\0 to bc\0.Abri
Rule of thumb: for interactive input always read lines.Jedlicka
Related: #5241289Deutero
E
27

scanf() reads exactly what you asked it to, leaving the following \n from the end of that line in the buffer where fgets() will read it. Either do something to consume the newline, or (my preferred solution) fgets() and then sscanf() from that string.

Evacuation answered 6/5, 2011 at 23:41 Comment(3)
If I want to use scanf(), how I can consume the new line? Thanks.Lamentation
Use something like %*[^\n]%*c at the end of the format to skip any characters up the newline, followed by the newline itself.Evacuation
@geekosaur: Just tacking %*[^\n]%*c on to the end of the format won't actually work if there are NO characters after the previous thing read and before the newline, as then the %*[^\n] will fail to match so the %*c will be skipped and the newline will still be left on the input. You need to do the %*c in a separate scanf call to make it work.Payson
B
10

scanf leaves whitespace in the input buffer, including new-line characters. To use fgets to read the next line you need to manually remove the rest of the current line:

int c;
do{
    c = getchar();
}while(c != EOF && c != '\n');
Bahr answered 7/5, 2011 at 0:8 Comment(2)
Yeah, that'll work too, but I personally just use fgets and then sscanf (as suggested by geekosaur) because it works "uniformly" for all sorts of data... not just single chars. And I like "generic, multi-purpose solutions". Cheers. Keith.Control
This does work for more then a character though - that's what the loop is for ;)Bahr
L
7

This is a more Simpler solution

scanf("%d",&loops);
while ((getchar()) != '\n'); //This will consume the '\n' char
//now you're free to use fgets
fgets(string,sizeof(string),stdin);
Lucretialucretius answered 5/11, 2019 at 2:15 Comment(1)
while ((getchar()) != '\n'); is an infinite loop on end-of-file.Signally
C
1

Geekosaur has answered your question well, I'm just pointing out another "problem" with your code.

The line s1[strlen(s1)] = '\0'; is a no-op if s1 is already properly null terminated BEFORE it executes.

But if s1 is NOT already properly null-terminated BEFORE this line executes (and you're unlucky) it will cause:

  • a SIGSEGV on a POSIX (*nix) system.
  • a GPF on Windows.

This is because strlen basicaly finds the index of the existing null-terminator and returns it! Here's a valid, unoptimized implementation of strlen:

int strlen(char *string) {
    int i = 0;
    while(string[i] != '\0') {
        ++i;
    }
    return i;
}

So... If you're REALLY worried about strings NOT being null-terminated then you'd do something like:

  • string[sizeof(string)]='\0'; on local automatic strings (where the compiler "knows" the size of the string);
  • or string[SIZE_OF_STRING] for all other strings, where SIZE_OF_STRING is (most commonly) a #define'd constant, or a variable which you maintain specifically to store the current-SIZE (not length) of a dynamically allocated string.

And if you're REALLY, REALLY, REALLY worried about strings not being null-terminated (like you're dealing with "dirty" libary methods (like Tuxedo's ATMI for example) you ALSO "clear" your "return strings" before passing them to the suspect library methods with:

  • before:memset(string, NULL, SIZE_OF_STRING);
  • invoke: DirtyFunction(/*out*/string);
  • after: string[SIZE_OF_STRING]='\0'

SIG11's are a complete biatch to find because (unless you "hook" them with a signal-processor and say otherwise, they cause Unix to hard-terminate your program, so you can't log anything (after the fact) to help figure out where-in-the-hell-did-that-come-from... especially considering that in many cases the line of code which throws the SIG11 is no-where-near the actual cause of the string loosing its null-terminator.

Does that make sense to you?

PS: WARNING: strncpy does NOT always null terminate... you probably meant strlcpy instead. I learned this the hard way... when a 60 million dollar billing run crashed.


EDIT:

FYI: Here's a "safe" (unoptimized) version of strlen which I'll call strnlen (I reckon this should be in string.h. Sigh.).

// retuns the length of the string (capped at size-1)
int strnlen(char *string, int size) {
    int i = 0;
    while( i<size && string[i]!='\0' ) {
        ++i;
    }
    return i;
}
Control answered 7/5, 2011 at 0:29 Comment(2)
I am novice at C, and the textbook doesn't talk about string and security much. Maybe my textbook isn't good enough :( Thank you for your professional advice :)Lamentation
Do not do string[sizeof(string)]='\0'! This will write write a \0 AFTER the allocated memory! sizeof(s1) is 101 and the the index of s1 goes from 0 to 100! The same problem occurs with the strnlen. If there is no \0 then it will return size, not size - 1 as mentioned in the comment!Abri
P
-1

Another way to ignore following newline character (due to hitting ENTER) after scanning the integer in variable label loops is:

scanf ("%d%*c", &loops);

Where, as per the man pages:

* Suppresses assignment. The conversion that follows occurs as usual, but no pointer is used; the result of the conversion is simply discarded.

This is very unlikely, but good habit to check for error during scanf:

errno = 0
scanf ("%d%*c", &loops);
if (errno != 0) perror ("scanf");
// act accordingly to avoid un-necessary bug in the code

For example in your code, loops is a local un-initialized variable, containing garbage. If scanf fails to fill the desired value, following while loop may run arbitrarily.

Presbyterate answered 31/12, 2019 at 1:53 Comment(1)
That's not how you check errors from scanfArrack
U
-1

scanf() leave the following \n from the end of that line in the buffer where fgets() will read it. To slove this we have to do something to consume the newline. Let's see the problem.

The problem

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}
Output:
Enter in scanf: Hello
Enter in fgets:

As you can see that it dons't show the fgets part. Let's see the solution.

Put getchar() between scanf and fgets()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}
Output:
Enter in scanf: Hello
Enter in fgets: Hello

If it's possible use scanf() after the fgets()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    fgets(input, 10, stdin);
    getchar();
    printf("Enter in scanf: ");
    scanf("%s", input);
}
Output:
Enter in fgets: Hello
Enter in scanf: Hello

Put fget() 2 times(This method don't work some time)

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
    fgets(input, 10, stdin)
}
Output:
Enter in scanf: Hello
Enter in fgets: Hello

Put sscanf() inseted of scanf()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    sscanf(hello, "%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}
Output:
Enter in sscanf: Hello
Enter in fgets: Hello
Ung answered 3/3, 2020 at 18:27 Comment(1)
Would you mind elaborating on this further? E.g. Providing an example.Dioecious
A
-2

just put scanf("%d\n",&loops);

instead of scanf("%d",&loops);

Advantage answered 9/9, 2016 at 0:27 Comment(2)
While this answer covers an important phenomenon of the original problem, it fails to provide some insight into the problem itslef. Thus, it is helpfull at best to the exect original problem and not really helpful in the long run. Please, add some more explanation to make your answer helpful to later readers looking for similar problems.Ardellaardelle
Do not put trailing white space at the end of a scanf() format string — it completely breaks the UI/UX because the input doesn't stop until you type something other than white space (and newlines are white space). It means the user has to predict what the next input needs to be. See [What is the effect of trailing white space in a scanf() format string?](https://mcmap.net/q/113178/-what-is-the-effect-of-trailing-white-space-in-a-scanf-format-string/10ths 5168)Stillness

© 2022 - 2024 — McMap. All rights reserved.