NO

Author Topic: scanf function problem  (Read 5749 times)

Yadav

  • Guest
scanf function problem
« on: May 08, 2013, 07:18:10 PM »
HI all,
         I was working on the do while loop and I have this code to find the prime factors of the number.
I am facing the problem with the scanf function which does not wait for a char to be entered from the keyboard and the main func terminates. If the variable "input" is declared as int and an integer is checked in the while loop then the do while loop function properly. Y is tis happening????? Please help.

                                printf("\nDo U wanna continue(y/n)??\n");
                      scanf("%c",&input);


CODE: FIND THE PRIME FACTORS

_Bool isPrime(int num)
{
   int i;
   if(num%2==0)
      return (0);

   for(i=2;i<num/2;i+=2)
   {
      if(num % 2==0)
         return (0);
   }

   return (1);
}

int main(void)
{
   //(e) Find teh prime numbers

   int num,i;
   unsigned char input;

   do
   {
      printf("Enter the number:\n");
      scanf("%d",&num);

      for(i=2;i<num;i++)
      {
         if(num%i==0)
         {
            if(isPrime(i) && isPrime(num/i))
               printf("\nThe prime factors are %d and %d",i,num/i);
         }
      }

      printf("\nDo U wanna continue(y/n)??\n");
      scanf("%c",&input);
   }
   while(input=='Y' || input =='y');
    return 0;
}

czerny

  • Guest
Re: scanf function problem
« Reply #1 on: May 08, 2013, 10:58:18 PM »
You know that the return char is a character too?

migf1

  • Guest
Re: scanf function problem
« Reply #2 on: May 10, 2013, 05:28:41 PM »
This is the infamous line-buffering status of stdin, which gives trouble to most of the standard input functions.

Characters typed in stdin are not delivered immediately; they are collected in a buffer until either the buffer gets filled or the user types an ENTER ('\n'). scanf() normally ignores any trailing ENTER but it does not remove it from the line buffer. Moreover, scanf() does NOT ignore leading ENTER.

So the 2nd time you invoke scanf() (getchar(), fgets(), etc) via your do-while loop, the first character scanf() founds in stdin is the leftover ENTER ('\n') form the previous user input. Since '\n' equals neither 'Y' nor 'y' your loop stops and your program ends.

An easy work-around is to use a custom input buffer, along with fgets() combined with sscanf(), something along the following lines:

Code: [Select]
#include <stdio.h>        // printf(), fflush(), fgets(), sscanf()
#include <ctype.h>       // tolower()
#include <stdlib.h>       // exit()

#define MAXINPUT    (255+1)

int main( void )
{
    char input[ MAXINPUT ] = {'\0'};
    char c;

    do {
        printf( "run again (y/)? " );
        fflush( stdout );

        fgets( input, MAXINPUT, stdin );    // read input as a string (error checking omitted for brevity)
        sscanf( input, "%c", &c );             // extract the info you need using sscanf()

    } while ( 'y' == tolower(c) );

    exit(0);
}


You can even wrap it up in a utility function, to be used every time you want to prompt for a char from stdin. Something like the following...

Code: [Select]
#include <stdio.h>        // printf(), fflush(), fgets(), sscanf()
#include <ctype.h>        // tolower()
#include <stdlib.h>        // exit()

#define MAXINPUT    (255+1)

/* ------------------------------------------------------------------ */
char prompt_for_char( const char *prompt )
{
    char input[ MAXINPUT ] = {'\0'};

    if ( prompt ) {
        printf( "%s", prompt );
        fflush( stdout );
    }

    return !fgets(input, MAXINPUT, stdin) ? '\0' : *input;
}
/* ------------------------------------------------------------------ */
int main( void )
{
    do {
    } while( 'y' == prompt_for_char("run again (y/)? ") );

    exit(0);
}


As I said above, the whole trick is to first read stdin as a whole in a big string and then use sscanf() on that string to extract the info you need (in my examples, that string is defined as: char input[MAXINPOUT] ).

Note however that if the user types more than MAXINPUT-1 characters in stdin, then the undesirable line-buffer effect will come back. So you may find it more useful to write a small function to read stdin by yourself. You may even have it displaying an optional prompt before reading the string. Something like that...

Code: [Select]
/* ------------------------------------------------------------------ */
int prompt_for_string( const char *prompt, char *str, const int maxlen )
{
    int i, c;

    if ( !str )
        return 0;    /* FALSE */
    if ( maxlen < 1 )
        return 0;    /* FALSE */

    if ( prompt ) {
        printf( "%s", prompt );
        fflush( stdout );
    }

    for (i=0; i < maxlen-1 && (c=getchar()) != '\n' && c != EOF; i++)
        str[i] = c;

    /* eat up any extra characters from stdin */
    if ( c != '\n' && c != EOF ) {
        while ( (c=getchar()) != '\n' && c != EOF )
            ;
    }

    /* nil-terminate str */
    str[i] = '\0';

    return 1;        /* TRUE */
}


Actually, the above function is a slightly improved version of the one I used in a small library I wrote a while ago, as a simplified mean of reading the stdin with C. The function inside the library suffers from a small bug, because it treats & compares EOF as a char instead of an int, which is a wrong thing to do. Thankfully it is extremely rare for a user to type the EOF char directly in the console.

The docs and the comments in that small library are all in Greek, but the code is pretty straight-forward I believe. Also there's a small ReadMe file written in English, including a simple code snippet for its usage.

That library also uses a fixed length for the custom input buffer, equal to 1024 chars, via the PF_MAXINPUT constant which is defined in the header file: prompt_for.h

If you'd like to read stdin into a dynamically grown custom string, then it would require a bit more work. Here's such a function I quickly wrote, which can hopefully give some insight (please note that I haven't tested extensively)...

Code: [Select]
/* -----------------------------------------------
 * Create a new string (nul-terminated array of chars) by reading the stdin.
 * Return the newly created string, or NULL on error.
 * Args:
 *    size_t *len:    If non-NULL, then it passes to the caller the length of
 *            the created string.
 *
 *    size_t *size:    If non-NULL, then it passes to the caller the size of
 *            the created string.
 *
 *    int beExact:    If 0 (false). then the size of the created string will
 *            be an exact multiple of the internally used alloc-ahead
 *            buffer. Most often it will be larger than: 1 + strlen()
 *
 *            If 1 (true), then the size of the created string will
 *            be exactly: 1 + strlen()
 * Notes:
 *    The returned string does NOT pretain the '\n' from stdin.
 */
char *s_new_from_stdin( int beExact, size_t *len, size_t *size )
{
    const int AHEAD = 16;            /* # of bytes for alloc-ahead buffer */
    int c, i,j;

    char *try = NULL;
    char *ret = NULL;
    char *retExact = NULL;

    /* create allocate ahead buffer */
    ret = malloc( AHEAD * sizeof(char) );
    if ( NULL == ret )
        goto fail;

    /* read stdin into ret until '\n' or EOF */
    j = 1;
    for (i=0; '\n' != (c=getchar()) && EOF != c; i++)
    {
        /* need another alloc-ahead buffer? */
        if ( i == (j * AHEAD)-1 )    /* -1 is for the terminating '\0' */
        {
            try = realloc(ret, (++j) * AHEAD);
            if ( NULL == try ) {
                free( ret );
                goto fail;
            }
            ret = try;
        }
        ret[i] = (char)c;
    }
    ret[i] = '\0';                /* nul-terminate ret */

    /* calc len & size of ret as if beExact == FALSE */
    size_t ln = i;
    size_t sz = (j * AHEAD);

    /* if beExact == TRUE, free unsued mem and update size */
    if ( beExact )
    {
        retExact = malloc( (i+1) * sizeof(char) );
        if ( NULL == retExact )
            goto fail;

        memcpy( retExact, ret, (i+1) );
        free(ret);
        ret = retExact;

        sz = i+1;
    }

    /* pass len and size to the caller */
    if ( len )  *len = ln;
    if ( size ) *size = sz;

    return ret;

fail:
    if (len)
        *len = 0;
    if (size)
        *size = 0;
    return NULL;
}
/* -----------------------------------------------
 *
 */
char *s_get_ahead( void ) {
    return s_new_from_stdin(0, NULL, NULL);
}
char *s_get_exact( void ) {
    return s_new_from_stdin(1, NULL, NULL);
}


It uses internally an alloc-ahead buffer, which grows dynamically as needed (by 16-byte chunks every time) as the user types in characters at stdin. A pointer to the created string is returned by the function (or NULL on error), while the len and size pointer arguments are used for passing to the caller the length and the size of the newly created string (or 0 on error).

The beExact boolean argument can be used for deciding whether the function should create a string with exactly so many characters as the user types in +1 for for the terminating '\0' (TRUE) or if it should leave the allocated memory as an exact multiple of the alloc-ahead buffer (FALSE).

The last 2 functions are just wrappers isolating the beExact behavior of the normal function, having the additional (dis)advantage that they do not return the length and the size of the created string. They can also be implemented as macros (if for example inlining is of greater importance).

In any case, the allocated memory returned by any of those functions should get explicitly freed after we are done working with the returned string...

Code: [Select]
int main( void )
{
    size_t len, size;
    char *s = s_new_from_stdin(0, &len, &size);

    if ( !s )
        puts( "<NULL>" );
    else {
        puts( s );
        printf( "len: %zu, size: %zu\n", len, size );
        free(s);
    }

    return 0;
}


Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2107
Re: scanf function problem
« Reply #3 on: May 10, 2013, 07:34:26 PM »
If you are lazy, just insert space before '%'.
Code: [Select]
scanf(" %c",&input);
May the source be with you

migf1

  • Guest
Re: scanf function problem
« Reply #4 on: May 10, 2013, 08:51:26 PM »
Alas this can't protect you against bad input.

Consider the following code...

Code: [Select]
...
    int n;
    char c;

    do {
        printf( "Enter an int: " );
        fflush( stdout );
        if ( 1 != scanf( " %d", &n ) )
            puts( "error" );
        else
            printf( "%d\n", n );

        printf( "run again (y/)? " );
        fflush( stdout );
        scanf( " %c", &c );
    } while ( 'y' == tolower(c) );
...

Say we accidentally type '12n' when we're prompted to enter an integer. That 'n' gets consumed by the subsequent scanf( " %c", &c); because it is not a whitespace.

In general scanf() is one of the most fragile functions in the standard C library, and personally I find it really unfortunate that all books insist in teaching it for interactive input. As you probably already know, nobody uses it in production code (unless combining it with other hacks), and even the c-faq advices against it for interactive input, since ages.

Here is a really good read, imho:  http://home.datacomm.ch/t_wolf/tw/c/getting_input.html

Yadav

  • Guest
Re: scanf function problem
« Reply #5 on: May 20, 2013, 06:44:38 PM »
Thx a lot for ur interest guys......I am getting to know a lot of things through this forum and certainly improving me as a programmer.