Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

User Input: Strings and Numbers [C]

user input fflush string

  • Please log in to reply
3 replies to this topic

#1 dcs

dcs

    CC Devotee

  • Just Joined
  • PipPipPipPipPipPip
  • 730 posts

Posted 27 September 2008 - 02:41 PM

User Input: Strings and Numbers [C]

Very early on when attempting to learn programming in C, you often do exercises that read in and print out strings and numbers. Now you might assume that C has a simple function that will get user input, and indeed it does, but there are a couple of gotchas that can easily trip up those new to programming in C.

There are two things to vigorously avoid, and unfortunately they are all too commonly seen:
char text[20];
[COLOR="Magenta"]gets(text); /* NEVER USE gets() */
scanf("%s", text); /* MANY HIDDEN GOTCHAS */[/COLOR]

So how do you do it right? The short answer is to use fgets to read a line of text. However, please note that when you enter text, you press the [Enter] or [Return] key and this character does not just vanish, it remains in the input buffer. For example:

[COLOR="Green"]#include [B]<[/B]stdio.h[B]>[/B][/COLOR]
 
[COLOR="Blue"]int [/COLOR][COLOR="Red"]main[/COLOR]([COLOR="Blue"]void[/COLOR])
{
   [COLOR="Blue"]char [/COLOR]text[[COLOR="Teal"]20[/COLOR]];
   [COLOR="Red"]fputs[/COLOR]([COLOR="Teal"]"enter some text: "[/COLOR], [COLOR="DarkOrchid"]stdout[/COLOR]);
   [COLOR="Red"]fflush[/COLOR]([COLOR="DarkOrchid"]stdout[/COLOR]); /* [url=http://c-faq.com/stdio/fflush.html]Question 12.4[/url] */
   fgets(text, [COLOR="Blue"]sizeof [/COLOR]text, [COLOR="DarkOrchid"]stdin[/COLOR]);
   [COLOR="Red"]printf[/COLOR]([COLOR="Teal"]"text = \"%s\"\n"[/COLOR], text);
   [COLOR="Blue"]return [/COLOR][COLOR="Teal"]0[/COLOR];
}

enter some text: hello world
text = "hello world
"


So when reading strings, you often want to remove this newline. Be careful though, because it is not always there! When the incoming text is one less than the size of the buffer, the newline is retained. You can search for the newline and overwrite it with a null terminator.

--- skimmers start here ---

[COLOR="Green"]#include [B]<[/B]stdio.h[B]>[/B]
#include [B]<[/B]string.h[B]>[/B]
[/COLOR] 
[COLOR="Blue"]int [/COLOR][COLOR="Red"]main[/COLOR]([COLOR="Blue"]void[/COLOR])
{
   [COLOR="Blue"]char [/COLOR]text[[COLOR="Teal"]20[/COLOR]];
   [COLOR="Red"]fputs[/COLOR]([COLOR="Teal"]"enter some text: "[/COLOR], [COLOR="DarkOrchid"]stdout[/COLOR]);
   [COLOR="Red"]fflush[/COLOR]([COLOR="DarkOrchid"]stdout[/COLOR]);
   [COLOR="Blue"]if [/COLOR]( [COLOR="Red"]fgets[/COLOR](text, [COLOR="Blue"]sizeof [/COLOR]text, [COLOR="DarkOrchid"]stdin[/COLOR]) != [COLOR="DarkOrchid"]NULL[/COLOR] )
   {
      [COLOR="Blue"]char [/COLOR]*newline = [COLOR="Red"]strchr[/COLOR](text, [COLOR="Teal"]'\n'[/COLOR]); [COLOR="Silver"]/* search for newline character */[/COLOR]
      [COLOR="Blue"]if [/COLOR]( newline != [COLOR="DarkOrchid"]NULL[/COLOR] )
      {
         *newline = [COLOR="Teal"]'\0'[/COLOR]; [COLOR="Silver"]/* overwrite trailing newline */[/COLOR]
      }
      [COLOR="Red"]printf[/COLOR]([COLOR="Teal"]"text = \"%s\"\n"[/COLOR], text);
   }
   [COLOR="Blue"]return [/COLOR][COLOR="Teal"]0[/COLOR];
}

enter some text: hello world
text = "hello world"

There are many other ways to do this, but this is very frequently recommended and always correct.

--- skimmers stop here ---


If you are reading a number, then first read in the input string and consequently follow it with a call to sscanf or strtol to convert it to a number.

[COLOR="Green"]#include [B]<[/B]stdio.h[B]>[/B][/COLOR]
 
[COLOR="Blue"]int [/COLOR][COLOR="Red"]main[/COLOR]([COLOR="Blue"]void[/COLOR])
{
   [COLOR="Blue"]char [/COLOR]text[[COLOR="Teal"]20[/COLOR]];
   [COLOR="Red"]fputs[/COLOR]([COLOR="Teal"]"enter some number: "[/COLOR], [COLOR="DarkOrchid"]stdout[/COLOR]);
   [COLOR="Red"]fflush[/COLOR]([COLOR="DarkOrchid"]stdout[/COLOR]);
   [COLOR="Blue"]if [/COLOR]( [COLOR="Red"]fgets[/COLOR](text, [COLOR="Blue"]sizeof [/COLOR]text, [COLOR="DarkOrchid"]stdin[/COLOR]) )
   {
      [COLOR="Blue"]int [/COLOR]number;
      [COLOR="Blue"]if [/COLOR]( [COLOR="Red"]sscanf[/COLOR](text, [COLOR="Teal"]"%d"[/COLOR], &number) == [COLOR="Teal"]1[/COLOR] )
      {
         [COLOR="Red"]printf[/COLOR]([COLOR="Teal"]"number = %d\n"[/COLOR], number);
      }
   }
   [COLOR="Blue"]return [/COLOR][COLOR="Teal"]0[/COLOR];
}

enter some number: 42
number = 42

The newline does not need to be removed, unless you choose to, because it is whitespace and is not part of a valid number. You may find the following related code snippets regarding the input of numbers and text useful:



To recap, here is why not to do what I said not to do! First, never use gets. It is included as a standard library function only as a holdover from pre-C89 code (the standards committee opted in the interest of not breaking existing code). But it is inherently and notoriously unsafe because there is no way to tell it the size of the buffer into which data will be read. So it is always a risk for buffer overflow, which may be used as an exploit. If you ever want to be employed as a programmer, it's best not to ever let a potential employer see you use this function!

As for scanf it is a truly complicated beast, and should certainly not be the first choice for someone just starting out programming in C. One of the first issues with scanf("%s", str); is it suffers from a very similar problem as gets, which is the incoming buffer size is not specified. If you didn't look up the description of %s you may not have known that this directive is whitespace-delimited. Which means that it if the user enters hello world, then str will be hello. There are more gotchas, such as if you are also using scanf to read integers, perhaps using scanf("%d", &i);, then the same whitespace issue may bite you. Remember that when you enter text you press the [Enter] or [Return] key and it remains in the input buffer. This can cause issues when mixed with other input functions such as getchar. There is also another insidious issue that comes up when using scanf: the "need" to flush the input buffer.

Or again, it is widely known by good programmers and potential employers that these are things to avoid. But if you still aren't convinced, listed below are many FAQs that say pretty much the same thing (mostly because I'm saying pretty much what they are saying).



And don't think I presented a comprehensive list of gotchas associated with scanf, these were merely some common ones!

There is another insidious issue that comes up when using scanf: the "need" to flush the input buffer. All too often we see fflush(stdin); used to "fix" this. But actually this one problem with scanf breeds another:



Edited by dcs, 27 September 2008 - 06:44 PM.

  • 0

#2 Aereshaa

Aereshaa

    CC Devotee

  • Just Joined
  • PipPipPipPipPipPip
  • 638 posts

Posted 05 October 2008 - 06:08 PM

Very nice, +repped.
  • 0

#3 outsid3r

outsid3r

    CC Devotee

  • Just Joined
  • PipPipPipPipPipPip
  • 494 posts

Posted 30 August 2009 - 03:00 AM

I consider this post really important, many people start C doing gets and other very unsafe functions, like i started, and i have read books of university teachers using many unsafe functions, which is a lot strange for a person of that graduation.
Rep+

Edit: well, i can't rep now after all :S
  • 0

#4 DStagata

DStagata

    CC Lurker

  • Just Joined
  • Pip
  • 2 posts

Posted 09 May 2010 - 02:57 PM

Really simple and informative, thank you. Most tutorials on this subject, I have read, take much more difficult approaches; this was extraordinarily helpful. :-)
  • 0





Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download