3

I am writing a program for fun (not for school), and am having a hard time figuring out why the scanf function isn't executing on every iteration of my loop - I've toyed with both 'for' loops and 'while' loops.

I know that depending on how I write the scanf function (i.e. scanf("%s", &variablename); VS scanf("%99[^\n]s", &variablename);) makes a difference, but I have tried everything and I'm desperate!

When I do a printf check on my input from the scanf, on every iteration it is only intaking one string per iteration, so if I enter two words in my first input, then it takes two iterations to process - one word per. Here is the segment of code I'm describing:

int main(void){
    int tries = 0;
    int score = 0;
    char question[100];
    char useranswer[100];
    const char *phrase = {"our favorite saying\0"};

    printf("\nQuestion #3 (10 points): What is our secret saying?\n");
        sleep(1);
        tries = 1;

    while (tries<=3){
        printf("YOUR ANSWER:");
        scanf("%s[^\n]", useranswer); 

        if(strncmp(useranswer, phrase, 15) != 0){
            printf ("Nope, try again!\n");
            printf("You have used %d out of 3 tries!\n", tries);
            if (tries == 2){
                printf("Here's your final hint:xxx...\n");
            }
            if (tries == 3){
            printf("You didn't get it. The answer is: our favorite saying!\n");
            }
            tries++;
        }   
        if (strncmp(useranswer, phrase, 15) == 0){
            printf("Damn, you're good.  Well done.\n");
            score += 10;
            break;
        }
    }   

The output of this code is:

Question #3 (10 points): What is our secret saying?
YOUR ANSWER:our favorite saying
Nope, try again!
You have used 1 out of 3 tries!
YOUR ANSWER:Nope, try again!
You have used 2 out of 3 tries!
Here's your final hint:xxx...
YOUR ANSWER:Nope, try again!
You have used 3 out of 3 tries!
You didn't get it. The answer is: our favorite saying!

(It only allowed me to input once, and I typed "our favorite saying".)

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
blarpsplat
  • 33
  • 4
  • 5
    `scanf("%99[^\n]%*c", useranswer); ` – BLUEPIXY Jun 28 '16 at 22:35
  • 1
    The `[...]` isn't an add-on for the `s` specifier... it's a different format specifier on its own. `%s` reads the next string of non-whitespace characters, and `%[^\n]` (*not* `%s[^\n]`) reads a string of any chars except `\n` -- so the code you have just reads each word one at a time (one per loop). – Dmitri Jun 28 '16 at 22:40
  • 3
    `"%s[^\n]"` is not a valid `scanf()` format. Suggest using `fgets(useranser, sizeof useranswer, stdin)` instead. – chux - Reinstate Monica Jun 28 '16 at 22:53
  • @chux Why is it not valid? – user253751 Jun 29 '16 at 00:21
  • @immibis Ahhh yes `"%s[^\n]"` _is_ valid, but certainly not useful nor certainly not what OP hoped for. `"%s[^\n]"` Would 1a) scan for white-space - throw them away, 1b) scan for non-white-space - save result in `useranswer`, possible over filling it, 1c) encounter an EOF or white-space and put that back into `stdin` 2) scan and attempt to match `[` with the previous whitespace and fail, ending the scan. Good catch. – chux - Reinstate Monica Jun 29 '16 at 00:27
  • `scanf` is behaving as described by the manual... hmmm, who'd have thought?! I can't understand the manual, though, so I'll keep focusing on learning the other parts of C and come back to learn about `scanf` once I've learnt those. – autistic Jun 29 '16 at 00:32
  • `int x = scanf("%99[^\n]", useranswer);` This first operation might cause `scanf` to return so it can't be merged with the discarding operation: `scanf("%*[^\n]"); getchar();` the former reading and discarding any excess characters beyond the 99 up to `'\n'` and the latter reading and discarding the newline itself. Again, these two operations cannot be merged into one `scanf` invocation because the left-most operations might cause failure meaning the operations following don't execute. BE CAREFUL with `scanf`! Knowledge is power, and it comes from the manpages in this case... – autistic Jun 29 '16 at 04:15
  • Also, following on from that example you'd want to write some logic using `x`... the manual section for `scanf` will tell you, in this case `x` can be `EOF`, `0` or `1` and you'll likely only want to use `useranswer` when `x` is `1` because the others correspond to read errors, end of file and empty fields. – autistic Jun 29 '16 at 04:24
  • Thank you everybody, @BLUEPIXY 's solution worked... Much appreciated. – blarpsplat Jun 29 '16 at 06:10
  • I changed the logic to follow @Bob suggestions as well, thank you again. – blarpsplat Jun 29 '16 at 06:10

1 Answers1

3

In comments you could find why your format specifier in scanf doesn't work.

An alternative is to use fgets instead, maybe in an helper function which takes care of some of the corner cases that can arise while reading user input:

#include <ctype.h>

char *read_line( char *buf, size_t n, FILE *pfin )
{
    ssize_t length = 0;
    int ch;

    if ( !buf || n == 0 )
        return NULL;
    /*  Consume trailing control characters, like '\0','\n', '\r', '\f'...
        but also '\t'. Note that ' ' is not skipped. */
    while ( (ch = fgetc(pfin)) != EOF  &&  iscntrl(ch) ) { }
    if ( ch == EOF )
        return NULL;
    /*  At least one char is printable  */
    *buf = ch;
    ++length;

    /*  Read from file till a newline or up to n-2 chars. The remaining chars
        are left in the stream buffer. Return NULL if no char is read.      */
    if ( fgets(buf + 1, n - 1, pfin) )
    {
        /*  Trim the string to the first control character                  */
        while ( !iscntrl(buf[length]) ) 
        {
            ++length;
        }
        buf[length] = '\0';       
    }
    return buf;
}

I'd change the following logic too. OP uses strncmp(useranswer, phrase, 15) multiple times, but that magic number 15 is lower then phrase's size so it ends up comparing only a substring.

while ( tries <= 3 ) {
    printf("YOUR ANSWER:");
    if ( !read_line(useranswer, sizeof useranswer, stdin) ) {
        printf("Error: Unexpected end of input.\n");
        exit(EXIT_FAILURE);
    }
    if( strcmp(useranswer, phrase) == 0 ) {
        printf("Damn, you're good.  Well done.\n");
        score += 10;
        break;
    } else {
        printf ("Nope, try again!\n");
        printf("You have used %d out of 3 tries!\n", tries);
        if (tries == 2) {
            printf("Here's your final hint:xxx...\n");
        }
        if (tries == 3) {
            printf("You didn't get it. The answer is: our favorite saying!\n");
        }
        tries++;
    }
}


As a final note, I found OP declaration of phrase a bit weird (maybe a typo):
const char *phrase = {"our favorite saying\0"};
//            string literals are already ^^ null terminated...

While we can use a simple array declaration, like:

const char phrase[] = "our favorite saying";

Consider also what values sizeof phrase returns in those two different cases.


Thanks to @chux for all the valuable hints and the interesting links provided:
https://stackoverflow.com/a/27729970/4944425
https://stackoverflow.com/a/28462221/4944425
And to @Dmitri for having pointed out in his comment that once we are sure that both the strings are null terminated, we can use strcmp instead of strncmp.
Community
  • 1
  • 1
Bob__
  • 12,361
  • 3
  • 28
  • 42
  • Consider http://stackoverflow.com/a/28462221/2410359 or http://stackoverflow.com/a/27729970/2410359 to remove the potential `'\n'`. `if ( useranswer[lstr - 1] == '\n')` is not safe. – chux - Reinstate Monica Jun 28 '16 at 23:28
  • @Dmitri if the user enters a longer string, the first part of the condition `lstr == strlen(phrase)` will be false. I followed OP's use of `strncmp` but note that he used `15` as a magic number. – Bob__ Jun 28 '16 at 23:29
  • @chux yes I didn't check for empty lines, my bad. – Bob__ Jun 28 '16 at 23:32
  • ok... but why not just use `strcmp()`, which won't indicate a match when the lengths differ anyway? Not sure why one would prefer `strncmp()` unless he needs to match on only part of the string.. – Dmitri Jun 28 '16 at 23:39
  • @dmitri In this case both strings are well formed (as far as I can tell following the logic of the code) so it wouldn't be necessary to limit the comparison. As I said, I only used the same function used by OP trying to point out his error (the use of fixed and arbitrary number), but again, I agree it isn't necessary. – Bob__ Jun 28 '16 at 23:50
  • Detail: When reading an "empty line", `useranswer[0]` will have `'\n'` in it and `lstr == 1`. For `useranswer[0]` to have `'\0'` in it occurs when the first text character read is a null character. This uncommon event is a hacker exploit. – chux - Reinstate Monica Jun 29 '16 at 02:27