473,320 Members | 1,979 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,320 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 14656
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

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

Similar topics

7
by: William L. Bahn | last post by:
I recently challenged one of my students to figure out a way to determine if fgets() actually received the entire contents of the input string. When he was having trouble figuring it out even after...
16
by: lovecreatesbeauty | last post by:
/* When should we worry about the unwanted chars in input stream? Can we predicate this kind of behavior and prevent it before debugging and testing? What's the guideline for dealing with it? ...
7
by: Hulo | last post by:
In a C program I am required to enter three numbers (integers) e.g. 256 7 5 on execution of the program. C:\> 256 7 5 There should be spaces between the three numbers and on pressing "enter",...
8
by: ais523 | last post by:
I use this function that I wrote for inputting strings. It's meant to return a pointer to mallocated memory holding one input string, or 0 on error. (Personally, I prefer to use 0 to NULL when...
125
by: jacob navia | last post by:
We hear very often in this discussion group that bounds checking, or safety tests are too expensive to be used in C. Several researchers of UCSD have published an interesting paper about this...
2
by: jou00jou | last post by:
Hi, I have trouble using sscanf and fgets to check for overflow. I will post the assignment specification so I could help whoever would kindly like to offer his/her help. ...
77
by: arnuld | last post by:
1st I think of creating an array of pointers of size 100 as this is the maximum input I intend to take. I can create a fixed size array but in the end I want my array to expand at run-time to fit...
4
by: Newbie | last post by:
Hello I need to enter a string of the form abc (a string of characters followed by EOF) #include<stdio.h> #include<stdlib.h> #include<string.h> #define MAX 100 int main(void) {
27
by: =?ISO-8859-1?Q?Tom=E1s_=D3_h=C9ilidhe?= | last post by:
I have a fully-portable C program (or at least I think I do). It works fine on Windows, but malfunctions on Linux. I suspect that there's something I don't know about the standard input stream...
0
by: DolphinDB | last post by:
Tired of spending countless mintues downsampling your data? Look no further! In this article, you’ll learn how to efficiently downsample 6.48 billion high-frequency records to 61 million...
0
by: ryjfgjl | last post by:
ExcelToDatabase: batch import excel into database automatically...
0
isladogs
by: isladogs | last post by:
The next Access Europe meeting will be on Wednesday 6 Mar 2024 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM). In this month's session, we are pleased to welcome back...
0
by: Vimpel783 | last post by:
Hello! Guys, I found this code on the Internet, but I need to modify it a little. It works well, the problem is this: Data is sent from only one cell, in this case B5, but it is necessary that data...
0
by: ArrayDB | last post by:
The error message I've encountered is; ERROR:root:Error generating model response: exception: access violation writing 0x0000000000005140, which seems to be indicative of an access violation...
1
by: PapaRatzi | last post by:
Hello, I am teaching myself MS Access forms design and Visual Basic. I've created a table to capture a list of Top 30 singles and forms to capture new entries. The final step is a form (unbound)...
0
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
0
by: Defcon1945 | last post by:
I'm trying to learn Python using Pycharm but import shutil doesn't work
1
by: Shællîpôpï 09 | last post by:
If u are using a keypad phone, how do u turn on JavaScript, to access features like WhatsApp, Facebook, Instagram....

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.