471,110 Members | 1,065 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 471,110 software developers and data experts.

Checking for excessive input when using fgets()

Hi All,

I am very new to C and have been working my way through a few C books with the aim of getting more knowledge in programming. However I have hit a wall and I am not sure how to get over it.

I have the following code, it is quite simple.

Expand|Select|Wrap|Line Numbers
  1. #include <stdio.h>
  2.  
  3. struct cat
  4. {
  5.      int age;
  6.      int height;
  7.      char name[20];
  8.      char father[20];
  9.      char mother[20];
  10. };
  11.  
  12.  
  13. int main()
  14. {
  15.  
  16.    struct cat myCat[2];
  17.    int i = 0;
  18.  
  19.    for(i = 0; i < 2 ; i++ )
  20.    {
  21.     printf("What is your cats name?\n");
  22.     fgets(myCat[i].name, 20, stdin);
  23.    }
  24.  
  25.   for(i = 0; i < 2 ; i++ )
  26.    {
  27.     printf("%s", myCat[i].name);
  28.    }
  29.  
  30. return 0;
  31. }
I understand how this code works without any problems. I do have issues regarding input though.

If I enter more that the allowed characters using the fgets function then the first myCat.name[i] is terminated with \0, this is what I would expect. The problem is that the STDIO buffer retains my extra input and enters it into the next fgets() function. How can I stop this?

Also how can I do some error checking on the fgets() functions to see if the user entered more than needed input and consequentaly reset the character array before asking them again?

I was thinking of finding the character array length and if it is equal to 19 checking the last element for the \0 terminator? I think this would do it but I am unsure how to clear the array so that it is empty and does not contain "junk" data.

Any help is greatly appreciated.
Oct 23 '07 #1
7 13287
gpraghuram
1,275 Expert 1GB
Hi All,

I am very new to C and have been working my way through a few C books with the aim of getting more knowledge in programming. However I have hit a wall and I am not sure how to get over it.

I have the following code, it is quite simple.

Expand|Select|Wrap|Line Numbers
  1. #include <stdio.h>
  2.  
  3. struct cat
  4. {
  5.      int age;
  6.      int height;
  7.      char name[20];
  8.      char father[20];
  9.      char mother[20];
  10. };
  11.  
  12.  
  13. int main()
  14. {
  15.  
  16.    struct cat myCat[2];
  17.    int i = 0;
  18.  
  19.    for(i = 0; i < 2 ; i++ )
  20.    {
  21.     printf("What is your cats name?\n");
  22.     fgets(myCat[i].name, 20, stdin);
  23.    }
  24.  
  25.   for(i = 0; i < 2 ; i++ )
  26.    {
  27.     printf("%s", myCat[i].name);
  28.    }
  29.  
  30. return 0;
  31. }
I understand how this code works without any problems. I do have issues regarding input though.

If I enter more that the allowed characters using the fgets function then the first myCat.name[i] is terminated with \0, this is what I would expect. The problem is that the STDIO buffer retains my extra input and enters it into the next fgets() function. How can I stop this?

Also how can I do some error checking on the fgets() functions to see if the user entered more than needed input and consequentaly reset the character array before asking them again?

I was thinking of finding the character array length and if it is equal to 19 checking the last element for the \0 terminator? I think this would do it but I am unsure how to clear the array so that it is empty and does not contain "junk" data.

Any help is greatly appreciated.

Hi,
After doing read you can call gets to clear stdin from having previously entered characters. see this code
Expand|Select|Wrap|Line Numbers
  1. #include <stdio.h>
  2.  
  3. struct cat
  4. {
  5.      int age;
  6.      int height;
  7.      char name[20];
  8.      char father[20];
  9.      char mother[20];
  10. };
  11.  
  12. void clear_stdin()
  13. {
  14. char read_rem[200];
  15. gets(read_rem);
  16. }
  17. int main()
  18. {
  19.  
  20.    struct cat myCat[2];
  21.    int i = 0;
  22.  
  23.    for(i = 0; i < 2 ; i++ )
  24.    {
  25.     printf("What is your cats name?\n");
  26.     fgets(myCat[i].name, 20, stdin);
  27.                 clear_stdin();/i have added this.
  28.    }
  29.  
  30.   for(i = 0; i < 2 ; i++ )
  31.    {
  32.     printf("%s", myCat[i].name);
  33.    }
  34.  
  35. return 0;
  36. }
  37.  
I think this will solve the purpose
Thanks
Raghuram
Oct 23 '07 #2
Banfa
9,065 Expert Mod 8TB
I was thinking of finding the character array length and if it is equal to 19 checking the last element for the \0 terminator? I think this would do it but I am unsure how to clear the array so that it is empty and does not contain "junk" data.
The is no need to clear the array. What you should do is check to see if the last character returned is a newline ('\n' ASCII 0x0A).

The user will have had to press return and fgets returns this character, if you have read the '\n' then you have read the entire line. You have not read a '\n' then there is more data to read from the line. Additionally you should check the return value from fgets to catch any errors.

Additionally you have a bug waiting to happen in you fgets line and loop conditions.

You have
Expand|Select|Wrap|Line Numbers
  1.     fgets(myCat[i].name, 20, stdin);
the problem is the 20. You have hard coded the buffer length, if you change the size of cat::name this code will need changing as well, if you don't and the buffer size is reduced you run a real risk of a segmentation fault.

This is better
Expand|Select|Wrap|Line Numbers
  1.     fgets(myCat[i].name, sizeof myCat[i].name, stdin);
the sizeof operator instructs the compiler to automatically insert the size of the cat::name array. If the buffer size is changed then the size passed to fgets is automatically changed.

There are other ways of doing this (you could us a #define) but this is my prefered method.

You loop end condition has the same problem, hardcoded to 2. Again would be better to have it change automatically with a change in array size and again this can be achieved either using the sizeof operator or using a #define.

What you want to achieve is only defining any given number in 1 place in your code, then if you need to change it you only have to change a single place and the rest of the code is automatically recompiled to the new size. This makes you code much more robust.
Oct 23 '07 #3
Banfa
9,065 Expert Mod 8TB
Expand|Select|Wrap|Line Numbers
  1. void clear_stdin()
  2. {
  3. char read_rem[200];
  4. gets(read_rem);
  5. }
  6.  
I think this will solve the purpose
I do not think this is a very good solution, it uses gets instead of fgets risking a buffer overrun error in read_rem and if it used fgets then it would run into the same problem the original code did. All be it that the user has to enter > 220 characters in order to run into problems.

Since this problem can be fixed absolutely so that it always works no matter how many charcters are input this "sort of works but not in every situation" approach is very poor design.

It is this sort of sloppy programming that leaves applications open to expoits using buffer overrun errors.
Oct 23 '07 #4
I have used this function to clear stdio, anybody have any thoughts on if this is a good or bad solution?

Expand|Select|Wrap|Line Numbers
  1. /*    This function flushes the stdio input buffer */
  2. void flush_input ( void ) 
  3.     /* getchar returns an int */ 
  4.     int ch; 
  5.     /* Read characters until there are none left */ 
  6.     do 
  7.     ch = getchar(); 
  8.     while ( ch != EOF && ch != '\n' ); 
  9.     /* Clear EOF state */ 
  10.     clearerr ( stdin ); 
  11. }
Oct 23 '07 #5
gpraghuram
1,275 Expert 1GB
I do not think this is a very good solution, it uses gets instead of fgets risking a buffer overrun error in read_rem and if it used fgets then it would run into the same problem the original code did. All be it that the user has to enter > 220 characters in order to run into problems.

Since this problem can be fixed absolutely so that it always works no matter how many charcters are input this "sort of works but not in every situation" approach is very poor design.

It is this sort of sloppy programming that leaves applications open to expoits using buffer overrun errors.
Hi Banfa,
Thanks for pointing it out.
Do u have a better solution for this as i think this is one of the common need in many programs.

Raghuram
Oct 23 '07 #6
Banfa
9,065 Expert Mod 8TB
I would do it like this.

The first thing to note is that it is nearly always necessary to encapsulate the input functions with your own functions. This is because verifying user input is actually a somewhat involved task and keeping the logic of doing this out of the main body of your code will make the rest of the code easier to maintain and therefore more robust.

Also if you do this you can unit test your input functions to provide a high level on confidence in them, where as if you write it into your code every time you end up with a mess that is hard to test properly.

So here is the function I would use to get a single string input (and this would then be the basis of functions to get integers and floats if the program required them)

Expand|Select|Wrap|Line Numbers
  1. int GetString(char *string, size_t size)
  2. {
  3.     char *fgr;
  4.     int result = 1; /* Assume OK */
  5.     char tmp[10];
  6.  
  7.     fgr = fgets(string, size, stdin);
  8.  
  9.     if (fgr != NULL)
  10.     {
  11.         if (string[strlen(string)-1] != '\n')
  12.         {
  13.             do
  14.             {
  15.                fgr = fgets(tmp, sizeof tmp, stdin);
  16.             }
  17.             while (fgr != NULL && tmp[strlen(tmp)-1] != '\n');
  18.  
  19.             if (fgr == NULL)
  20.             {
  21.                 result = 0;
  22.             }
  23.         }
  24.     }
  25.     else
  26.     {
  27.         result = 0; /* fgets returned error of some sort (could be eof) */
  28.     }
  29.  
  30.     return result;
  31. }
  32.  
Oct 23 '07 #7
Banfa
9,065 Expert Mod 8TB
I have used this function to clear stdio, anybody have any thoughts on if this is a good or bad solution?

Expand|Select|Wrap|Line Numbers
  1. /*    This function flushes the stdio input buffer */
  2. void flush_input ( void ) 
  3.     /* getchar returns an int */ 
  4.     int ch; 
  5.     /* Read characters until there are none left */ 
  6.     do 
  7.     ch = getchar(); 
  8.     while ( ch != EOF && ch != '\n' ); 
  9.     /* Clear EOF state */ 
  10.     clearerr ( stdin ); 
  11. }
This solution seems fine however you have to be sure that stdin needs flushing before calling it, if you call it when there is no data on stding getchar waits for input.

There is still a case for encapsulating the input command as I did but using your flush_input combined with my original function gives

Expand|Select|Wrap|Line Numbers
  1. int GetString(char *string, size_t size)
  2. {
  3.     char *fgr;
  4.     int result = 1; /* Assume OK */
  5.  
  6.     fgr = fgets(string, size, stdin);
  7.  
  8.     if (fgr != NULL)
  9.     {
  10.         if (string[strlen(string)-1] != '\n')
  11.         {
  12.             flush_input();
  13.         }
  14.     }
  15.     else
  16.     {
  17.         result = 0; /* fgets returned error of some sort (could be eof) */
  18.     }
  19.  
  20.     return result;
  21. }
  22.  
Which is better because it requires less stack data (although an extra stack frame).
Oct 23 '07 #8

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

7 posts views Thread by William L. Bahn | last post: by
8 posts views Thread by ais523 | last post: by
125 posts views Thread by jacob navia | last post: by
77 posts views Thread by arnuld | last post: by
4 posts views Thread by Newbie | last post: by
27 posts views Thread by =?ISO-8859-1?Q?Tom=E1s_=D3_h=C9ilidhe?= | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.