Common StackOverflow errors #2 scanf

Ah yes, the evil that is scanf. I always try to avoid using it because of its quirks. Many , many common misuses of it.

#1 Not passing pointers

float f;
scanf("%f", f);

This is instant UB, the program usually crashes right there.

Spotted by most compilers

so1.c:13:15: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘double’ [-Wformat=]

As stated before though, a lot of starting devs ignore warnings, treating them as nags rather than the compiler trying to help them.

#2 mismatched scan type and variable

float f;
scanf("%d", &f);

Most compilers spot this

so1.c:13:15: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘float *’ [-Wformat=]

This code will run but give surprising answers, its still UB though

#3 ignoring the return

      int i;
printf("whats your favorite number? ");
scanf("%d", &i);
printf("%d\n", i);

Spotted by the compiler

so1.c:15:7: warning: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Wunused-result]

scanf returns the number of variables it successfully populated

Here we have done everything right but did not check that scanf read anything. If we enter junk, we get a surprise:

pm100@paul-think:~$ ./a.out
whats your favorite number? no
32767
pm100@paul-think:~$

Should have checked that scanf returned 1

#4 Passing char pointers that point nowhere valid

   printf("Whats your name? ");
char *nameBuff;
scanf("%s", nameBuff);

This is classic Undefined Behavior. Interestingly this worked with gcc, no warnings or any complaint. And ran cleanly — such is UB

MSVC fataled it:

error C4700: uninitialized local variable ‘nameBuff’ used

There is a scanf format that can help, but this is not implemented everywhere (I’m looking at you MSVC)

  printf("Whats your name? ");
char *nameBuff;
scanf("%ms", &nameBuff);

This will actually allocate a large enough buffer for you, remember to free it when done

#5 not understanding scanf behavior

scanf reads bytes from stdin until it is satisfied that it has read what it needs, but no more. In particular

  • if invalid data is input (say ‘nope’ in the example above) it is still in the buffer and will be read again on the next call to scanf
  • if a value that doesnt ‘eat’ newlines is reaqd the newline is still there waiting to be read

Suppose we want to make an app that asks the user their name, then for a number (checking that it is a valid number), asking if it’s what they want. If it is then calculate the square of it otherwise ask for another number.

int main()
{

char nameBuff[50];
printf("Whats your name? ");
scanf("%49s", nameBuff);

int i = 0;
while(1){
printf("%s, whats your favorite number? ", nameBuff);
scanf("%d", &i);
printf("%s, is %d OK?\n", nameBuff, i);
char ch;
scanf("%c", &ch);
if(ch == 'y')
break;
}
printf("%d squared is %d\n", i, i*i);
}

This is very much like many SO questions, on the face of it it looks fine. Let’s give it a spin:

Whats your name? dave
dave, whats your favorite number? 42
dave, is 42 OK?
dave, whats your favorite number? 11
dave, is 11 OK?
dave, whats your favorite number? n
dave, is 11 OK?
dave, whats your favorite number? y
dave, is 11 OK?
11 squared is 121

Well thats odd! After asking if 42 is OK it doesnt wait for a response, it just asks again. I tried a few things to no avail, then I tried answering ‘y’ to the number prompt and it worked.

Lets try it again, what happens if I dont enter a number?

Whats your name? dave
dave, whats your favorite number? hey
dave, is 0 OK?
dave, whats your favorite number? dave, is 0 OK?
dave, whats your favorite number? dave, is 0 OK?
0 squared is 0

Even odder. Lets add a few diagnostics to see whats going on

    while(1){
printf("%s, whats your favorite number? ", nameBuff);
int ret1 = scanf("%d", &i);
printf("scan1 return = %d i = %d\n", ret1, i);
printf("%s, is %d OK?\n", nameBuff, i);
char ch;
int ret2 = scanf("%c", &ch);
printf("scan2 return = %d ch = 0x%02X\n",ret2, ch);
if(ch == 'y')
break;
}

This traces the return value and the expected argument value on each scanf call.

Whats your name? dave
dave, whats your favorite number? 42
scan1 return = 1 i= 42
dave, is 42 OK?
scan2 return = 1 ch=0x0A
dave, whats your favorite number? y
scan1 return = 0 i= 42
dave, is 42 OK?
scan2 return = 1 ch=0x79
42 squared is 1764

All is now clear.

  • we read 42 fine, but there is anenter in the buffer (its not part of the number so its left there)
  • The second scanf reads that enter (hex 0A). Its not ‘y’ so we loop around
  • Now I entered ‘y’ at the number prompt. Thats fails (0 is returned, note that i is untouched)
  • We then drop through to the second scanf it happily reads the ‘y’ (Hex 0x79) out of the buffer. And we are done

Heres the ‘hey’ one:

Whats your name? dave
dave, whats your favorite number? hey
scan1 return = 0 i= 0
dave, is 0 OK?
scan2 return = 1 ch=0x68
dave, whats your favorite number? scan1 return = 0 i= 0
dave, is 0 OK?
scan2 return = 1 ch=0x65
dave, whats your favorite number? scan1 return = 0 i= 0
dave, is 0 OK?
scan2 return = 1 ch=0x79
0 squared is 0

I leave it to you to work out what’s going on.

How to fix this?

See

    while(1){
while(1){
printf("%s, whats your favorite number? ", nameBuff);
int ret1 = scanf("%d", &i);
printf("scan1 return = %d i = %d\n", ret1, i);
if(ret1 == 1) // check for a good number
break;
printf("Enter a number please !\n");
scanf("%*[^\n]"); // clear the whole buffer
}
printf("%s, is %d OK? ", nameBuff, i);
char ch;
int ret2 = scanf(" %c", &ch); // note leading space
printf("scan2 return = %d ch = 0x%02X\n",ret2, ch);
if(ch == 'y')
break;
}

Two changes

  • Check the return of the numeric prompt to make sure we read a good number. If not then flush the input buffer by asking scanf to read the entire line but ignore it. Now ask again.
  • change the format string on scan2 to “ %c”. That leading space character makes scanf eat the extra whitespace

Now it all works.

Whats your name? dave
dave, whats your favorite number? hey
scan1 return = 0 i = 0
Enter a number please !
dave, whats your favorite number? 42
scan1 return = 1 i = 42
dave, is 42 OK? y
scan2 return = 1 ch = 0x79
42 squared is 1764

Comments

Leave a comment