0

So I'm stuck on this one. When I put an fgets after scanf, the fgets couldn't store space inputs. A quick googling taught me that This happens because every scanf() leaves a newline character in a buffer that is read by the next scanf. and putting a space or newline at the end of the control string fixes it... Yep it did but the problem I am facing is that if there's a printf between scanf and fgets, the fgets work before the printf.

So, I need an explanation for this behavior + any solutions/workarounds if any.

Compiler/Platform used: gcc 11/Linux

Here is the code (LOOK AT LINE 24-27 for example)

/*Write a program to create a structure of employees containing the following data members: Employee ID, Name, Age, Address, Department and Salary.
 Input data for 10 employees and display the details of the employee from the employee ID given by the user.*/

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

int main()
{
   int i=0;
   typedef struct employee
   {
      long int id;
      char name[30];
      int age;
      char address[100];
      char dept[30];
      long int salary;
   }emp;

   emp list[5];

   for (i = 0; i<10;i++)
   {
      printf("Enter id of employee %d : ", i+1);
      scanf("%ld ", &(list[i].id));
      printf("Enter name of employee %d : ", i+1);
      fgets(list[i].name, 30, stdin);
      printf("Enter age of employee %d : ", i+1);
      scanf("%d ", &(list[i].age));
      printf("Enter address of employee %d : ", i+1);
      fgets(list[i].address, 100, stdin);
      printf("Enter dept of employee %d : ", i+1);
      fgets(list[i].dept, 30, stdin);
      printf("Enter salary of employee %d : ", i+1);
      scanf("%ld ", &list[i].salary);
   }

   long int emp_id;
   int flag=0;
   printf("Enter employee id to be searched: ");
   scanf("%ld", &emp_id);

   // Linear search
   for (i=0; i<10; i++)
   {
      if (list[i].id==emp_id)
      {
         printf("Employee found!!");
         flag=1;
         printf("Id : %ld \n", list[i].id);
         printf("Name : %s \n", list[i].name);
         printf("Age : %d\n", list[i].age);
         printf("Address : %s \n", list[i].address);
         printf("Dept : %s \n", list[i].dept);
         printf("Salary : %ld \n", list[i].salary);
      }
   }
   if (flag==0)
   {
      printf("Employee not found!!");
   }
}

Output

enter image description here

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
karthik nair
  • 134
  • 1
  • 9
  • Generally, questions are of higher quality if they provide a [mre] of the problem. I have the impression that it should be possible to demonstrate the problem with less code. – Andreas Wenzel Mar 05 '22 at 22:25
  • Please post the output of the program as text, not as an image. You may want to read this: [Why not upload images of code/errors when asking a question?](https://meta.stackoverflow.com/q/285551/12149471) – Andreas Wenzel Mar 05 '22 at 22:29
  • 1
    The answer to the title is, "just don't". `fgets` and `scanf` were not designed to work well together. If you're going to use `fgets`, then read every line of the input with `fgets`. For those lines that contain numbers, use `sscanf` or functions like `strtol` to convert the strings to numbers. For those lines that are just strings, remember to [remove the newline that `fgets` puts in the buffer](https://stackoverflow.com/questions/2693776). – user3386109 Mar 05 '22 at 23:11
  • Rather than magic numbers like 100, `fgets(list[i].address, sizeof list[i].address, stdin);` is better. – chux - Reinstate Monica Mar 06 '22 at 14:35

2 Answers2

3

Your problem is that the convention you use with the space at the end of the scanf format string does not really consume the \n. As a result fgets consumes it and skips the string input. It really has nothing to do with the printf.

The way to solve this is to explicitly consume the newline like so: scanf("%ld%*c", &i). The * is a suppressing character which tells scanf to consume the input but not try to assign it to a variable and the c conversion specifier asks to read any character. So we ask scanf to read one char after the long int but not assign it to a variable. If this char happens to be a newline everything will be OK (But if not - trouble again). Please read the documentation at scanf, fscanf, sscanf, scanf_s, fscanf_s, sscanf_s.

I would also recommend you add a call to fflush(stdout); after every printf because your are not using a newline to terminate your prints.

As a final note. It is probably best that you read all your input using fgets which will properly handle any input terminated by a newline (also on systems where the newline is not just one character). Once you read the input as a string you can use sscanf or other functions to correctly parse it.

Sebastian Cabot
  • 1,812
  • 14
  • 18
  • 1
    `scanf("%ld%*c", &i)` will only work if the next character is the new line character. Although your solution will probably work in most cases, a more robust solution would keep reading characters until it encounters a newline character. For example, your solution will fail if the user enters `32 ` as input (note the trailing space character). – Andreas Wenzel Mar 05 '22 at 23:09
  • @AndreasWenzel You are correct and I mentioned this in the answer. That is also why I added the recommendation to read everything using fgets and parse it later – Sebastian Cabot Mar 05 '22 at 23:11
2

Yep it did but the problem I am facing is that if there's a printf between scanf and fgets, the fgets work before the printf.

That description is not accurate.

The problem is rather the following line:

scanf("%ld ", &(list[i].id));

As stated in the documentation of the scanf function, the space character in the format string will not only match a single space character, but as many as possible. Therefore, this line of code will first read a number and then continue reading and discarding whitespace characters until a non-whitespace character is encountered.

Since you are probably using a terminal that is line-based, this means that it will have to enter at least a whole line of extra input, before the function scanf finds a non-whitespace character and returns.

Therefore, I recommend that you remove the space character from the format string, and that you use a different method of discarding the leftovers of the line after the scanf function call. You could simply call getchar to consume the newline character, and then discard it. However, this will only work if the next character actually is the newline character. A more robust method of discarding the leftovers of a scanf function call would be the following loop:

int c;

do
{
    c = getchar();

} while ( c != EOF && c != '\n' );

This loop will continue discarding characters until it encounters the newline character, or if end-of-file or an error occurs.

After fixing your program, it should look like this:

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

void discard_remainder_of_line( void )
{
    int c;
    
    do
    {
        c = getchar();

    } while ( c != EOF && c != '\n' );
}

int main()
{
   int i=0;
   typedef struct employee
   {
      long int id;
      char name[30];
      int age;
      char address[100];
      char dept[30];
      long int salary;
   }emp;

   emp list[5];

   for (i = 0; i<10;i++)
   {
      printf("Enter id of employee %d : ", i+1);
      scanf("%ld", &(list[i].id));
      discard_remainder_of_line();
      printf("Enter name of employee %d : ", i+1);
      fgets(list[i].name, 30, stdin);
      printf("Enter age of employee %d : ", i+1);
      scanf("%d", &(list[i].age));
      discard_remainder_of_line();
      printf("Enter address of employee %d : ", i+1);
      fgets(list[i].address, 100, stdin);
      printf("Enter dept of employee %d : ", i+1);
      fgets(list[i].dept, 30, stdin);
      printf("Enter salary of employee %d : ", i+1);
      scanf("%ld", &list[i].salary);
      discard_remainder_of_line();
   }

   long int emp_id;
   int flag=0;
   printf("Enter employee id to be searched: ");
   scanf("%ld", &emp_id);
   discard_remainder_of_line();

   // Linear search
   for (i=0; i<10; i++)
   {
      if (list[i].id==emp_id)
      {
         printf("Employee found!!");
         flag=1;
         printf("Id : %ld \n", list[i].id);
         printf("Name : %s \n", list[i].name);
         printf("Age : %d\n", list[i].age);
         printf("Address : %s \n", list[i].address);
         printf("Dept : %s \n", list[i].dept);
         printf("Salary : %ld \n", list[i].salary);
      }
   }
   if (flag==0)
   {
      printf("Employee not found!!");
   }
}

As you can see, at least the first part of your program appears to work now:

Enter id of employee 1 : 264
Enter name of employee 1 : John Doe
Enter age of employee 1 : 22
Enter address of employee 1 : 43 Third Street
Enter dept of employee 1 : Computer Science
Enter salary of employee 1 : 5000
Enter id of employee 2 : Jane Doe
Enter name of employee 2 : 21
Enter age of employee 2 : 
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39