PDA

View Full Version : scanf() seems to be skipped




timjver
Jan 25, 2012, 03:27 PM
I'm building a program that calculates wether the entered value is a prime number or not. This is the setValue method:

-(void)setValue
{
Boolean instructionsNeeded = NO;

printf("\nType in a number to check if it's prime.\n");

for ( ; ; )
{
if (instructionsNeeded == YES)
{
printf("Please insert a positive integer, smaller than 2^63 (%li).\n", 9223372036854775807);
}

scanf("%lli", &value);

if (value < 1)
{
printf("Oops, your value is too small. ");
instructionsNeeded = YES;
}
else if (value > 9223372036854775807)
{
printf("Oops, your value is too big. ");
instructionsNeeded = YES;
}
else

break;
}

[self primeTester:value];
}

Value is declared in the header file, it's of type long long. When -15 is inserted, the output is as expected:

Type in a number to check if it's prime.
-15
Oops, your value is too small. Please insert a positive integer, smaller than 2^63 (9223372036854775807).

However, when entering a number that's not of type long long, i.e. 0.2, scanf() seems to be skipped:

Type in a number to check if it's prime.
0.2
Oops, your value is too small. Please insert a positive integer, smaller than 2^63 (9223372036854775807).
Oops, your value is too small. Please insert a positive integer, smaller than 2^63 (9223372036854775807).
Oops, your value is too small. Please insert a positive integer, smaller than 2^63 (9223372036854775807).

Etcetera.

I've searched for ways to check wether the value is a value my program can deal with (type long long) and to actually wait for another value to be entered, but I found nothing suitable. I also tried a lot, like using another variable called input that was of type long double, but nothing worked.

Also, when I enter a number higher than 2^63, i.e. 100000000000000000000, my program doesn't tell it's actually too big:

Type in a number to check if it's prime.
100000000000000000000
No, 9223372036854775807 is not a prime number, 9223372036854775807 = 7^2 x 73 x 127 x 337 x 92737 x 649657.

Help would definitely be appreciated.



Sander
Jan 25, 2012, 03:41 PM
There are two problems.

The first is that your scanf() will eat up characters until it bumps into one which is not a digit. In your 0.2 case, it will gobble the 0 and stop at the dot. The nasty thing is that this dot will remain in the input buffer, so the next time round it will run into that same dot again, so it will not modify "value" at all.

There are several ways you can solve this, one is by first getting a whole line of text from the user and parsing it afterwards. Also, note that scanf() returns the number of input items matched (in your second and all following loops, this would be zero). You could use that to check for errors too.

Second, when you parse a really big number into a 64 bit signed integer, and the user enters something which won't fit in a signed 64 bit integer, how would your check work..?

robvas
Jan 25, 2012, 03:47 PM
Try having the user enter a string, and then convert it to a number.

timjver
Jan 26, 2012, 09:42 AM
Thanks for the (quick) answers.

The first is that your scanf() will eat up characters until it bumps into one which is not a digit. In your 0.2 case, it will gobble the 0 and stop at the dot. The nasty thing is that this dot will remain in the input buffer, so the next time round it will run into that same dot again, so it will not modify "value" at all.

That explains...

Try having the user enter a string, and then convert it to a number.

one is by first getting a whole line of text from the user and parsing it afterwards.

I couldn't find a proper way to do this. The internet tells me there are several ways of doing this, but I couldn't get anything to work. Any suggestions?

Also, note that scanf() returns the number of input items matched (in your second and all following loops, this would be zero). You could use that to check for errors too.

Thanks, I didn't know this. I added this to my method:

if (scanf(" %lli", &value) == 0)
{
printf("Your value is not valid. ");
instructionsNeeded = YES;
}

The sentence above is printed, but still it's repeated for an infinite amount of times.

Second, when you parse a really big number into a 64 bit signed integer, and the user enters something which won't fit in a signed 64 bit integer, how would your check work..?

I was wondering the same exact thing. I could probably check this by using the length method of the string.

chown33
Jan 26, 2012, 09:52 AM
I couldn't find a proper way to do this. The internet tells me there are several ways of doing this, but I couldn't get anything to work. Any suggestions?


fgets().

If you can't get it to work:
1. Post your code.
2. Describe what you expected to happen.
3. Describe what actually happened.

We can't debug anything from the statement "I couldn't get anything to work". The only reply to that statement is "Exactly what did you try?".

jaduff46
Jan 26, 2012, 01:46 PM
as chown33 indicates, use fgets() to get the string in, or gets() if you're taking from stdin.

Then use sscanf() to get the number out of the string.

int sscanf(char *string, char *format, arg1, arg2, ...).

There are examples all over the place on how to do this.

timjver
Jan 26, 2012, 02:14 PM
We can't debug anything from the statement "I couldn't get anything to work". The only reply to that statement is "Exactly what did you try?".

As I said, I've tried several ways to do this. However, those were answers to questions asked by other people, which situation didn't completely match mine. For example, I tried putting this in my setValue method:

scanf(" %s", &input);
value = [input longLongValue];

However, when running this program, Xcode points to value = [input longLongValue]; and says Thread 1: Program received signal: "EXC_BAD_ACCESS".

----------

fgets().

as chown33 indicates, use fgets() to get the string in, or gets() if you're taking from stdin.

Then use sscanf() to get the number out of the string.

int sscanf(char *string, char *format, arg1, arg2, ...).

There are examples all over the place on how to do this.

As I'm a so-called macrumors newbie, I'm also a programming newbie. Could you explain how this would be done in my code? Or am I trying to do something I'm just not ready for yet?

chown33
Jan 26, 2012, 02:51 PM
As I'm a so-called macrumors newbie, I'm also a programming newbie. Could you explain how this would be done in my code? Or am I trying to do something I'm just not ready for yet?

Do you have any code that calls fgets()? If so, post it. If not, then you should read the reference docs for fgets(), figure out how it works (which isn't that hard), then try writing some code that uses it.

If you haven't tried fgets(), and haven't written any code that uses it, then trying it is the next step.

You could pretty easily find examples of fgets() on the web, too. You might write less code, or you might be more confused if you don't understand the code you found.

In any case, the next step is plain: try using fgets().

What Have You Tried? (http://WhatHaveYouTried.com/)


scanf(" %s", &input);
value = [input longLongValue];

Show the type declaration for the 'input' variable. We could guess (and I have my suspicions), but guessing is a poor way to debug.

And I strongly doubt that the %s pattern to scanf() will do what you seem to think it does. I suspect you have a misconception regarding %s and scanf.


I'm also a programming newbie
What book or tutorial are you learning from? Be specific: title, author, edition, or URL of tutorial.

timjver
Jan 27, 2012, 05:52 PM
What book or tutorial are you learning from? Be specific: title, author, edition, or URL of tutorial.

Programming in Objective-C 2.0, Third edition, by Stephen G. Kochan. I've currently finished the first seven chapters, I think the book is great.

What Have You Tried? (http://www.whathaveyoutried.com/)

Thanks, excellent read. :)

Show the type declaration for the 'input' variable.

I declared input by:

NSString *input = [NSString new];

as I didn't know another way to do this. However, further in this post, I do.

Do you have any code that calls fgets()? If so, post it. If not, then you should read the reference docs for fgets(), figure out how it works (which isn't that hard), then try writing some code that uses it.

I've read references about fgets(), but as I'm not a native English speaker (still learning), I didn't completely understand what it does. I continued searching the internet for fgets(), and got here (http://beej.us/guide/bgc/output/html/multipage/gets.html). Using that information, I wrote this code:

int main (int argc, const char * argv[])
{

@autoreleasepool {

char s[100];

printf("%s", fgets(s, 17, stdin));
}
return 0;
}

The output was as expected:

1234
1234

Getting to understand sscanf() was easy. Using the functions fgets() and sscanf(), I got to the following code:

-(void)setValue
{
Boolean instructionsNeeded = NO;
char input [0];

printf("\nType in a number to check if it's prime.\n");

for ( ; ; )
{
if (instructionsNeeded == YES)
{
printf("Please insert a positive integer.\n");
}

float realInput;

fgets(input, 17, stdin);

sscanf(input, " %lli", &value);
sscanf(input, " %f", &realInput);

if (value != realInput || value < 0)
{
printf("Your value is not valid. ");
instructionsNeeded = YES;
continue;
}

break;
}
[self primeTester:value];
}

Output:

Type in a number to check if it's prime.
7.2
Your value is not valid. Please insert a positive integer.
2349873 // checking if the program still works for integers
No, 2349873 is not a prime number, 2349873 = 3^2 x 191 x 1367.

This looked very good. However, it goes wrong when inserting at least 9 digits, output:

Type in a number to check if it's prime.
99999999
No, 99999999 is not a prime number, 99999999 = 3^2 x 11 x 73 x 101 x 137.

Type in a number to check if it's prime.
100000000
sharedlibrary apply-load-rules all
(gdb)

Xcode now points to the setValue method and says Thread 1: Program received signal: "EXC_BAD_ACCESS". Changing realInput to type double or long double makes it even worse: it causes Xcode to give the error message at 11 digits, and the realInput of normal integers are supposedly suddenly different than the value, since my program qualifies the input as invalid:

Type in a number to check if it's prime.
23
Your value is not valid. Please insert a positive integer.

Why is this? Long double is (if I'm right) supposed to be able to hold much larger values than 10.000.000.000.

Apart from this, when inserting just a random key, this happens:

Type in a number to check if it's prime.
@
Oops, your value is too small. Please insert a positive integer.

I (partly) solved this by changing the if() statement a little bit:

if (value != realInput || value <= 0)
{
printf("Your value is not valid. ");
instructionsNeeded = YES;
continue;
}

Output:

Type in a number to check if it's prime.
@
Your value is not valid. Please insert a positive integer.

The only problem is, the number 0 shouldn't be interpreted as an invalid number. So, I somehow have to check wether the user inserted '0' before the if() statement.

I searched the web, but didn't find anything, and adding || input != '0' to the if() statement didn't work, because according to Xcode I'm comparing a pointer with an integer. Fair enough, but if I try this:

char zero = '0';

if (value != realInput || value <= 0 || input != zero)
{
printf("Your value is not valid. ");
instructionsNeeded = YES;
continue;
}

Xcode tells me the same thing, which this time, I do not understand. Why does Xcode tell me I'm comparing a pointer with an integer, while I'm trying not to?

chown33
Jan 27, 2012, 07:18 PM
I declared input by:

NSString *input = [NSString new];

as I didn't know another way to do this. However, further in this post, I do.

Good, because neither fgets() nor scanf() works with NSString objects. In fact, they don't work with any Objective-C objects.


-(void)setValue
{
Boolean instructionsNeeded = NO;
char input [0];


What do you intend the red-hilited code to mean?

When I read it, I see "Declare an array of chars named input, that holds zero chars.".

If an array only holds zero chars, how many characters do you think fgets() or anything else will be able to store there, without overrunning it and damaging other memory locations?

fgets(input, 17, stdin);

17? Is the input array really that long?

If input isn't that long, then you need to make input long enough to hold what you're asking fgets() to read. It won't hurt to have array longer than 17, but having it shorter is very harmful indeed. It might give bad results (which you saw) or it might crash (which you saw as EXC_BAD_ACCESS). Or it might seem to work fine for some values but not others (which you also saw).

If input isn't long enough to hold what you tell fgets() to read, then the array will be overrun, with grave consequences. You're basically writing data to some other location in memory, which is almost always a Very Bad Thing.

You could look at the sizeof() function instead of making a wild guess of 17.


All the rest of the code and the output after this point is unreliable, because you're overwriting unknown other variables.

KnightWRX
Jan 27, 2012, 08:44 PM
or gets() if you're taking from stdin.

Never, ever use gets(). Ever. Under no circumstances. Straight from the man page :

SECURITY CONSIDERATIONS
The gets() function cannot be used securely. Because of its lack of
bounds checking, and the inability for the calling program to reliably
determine the length of the next incoming line, the use of this function
enables malicious users to arbitrarily change a running program's func-
tionality through a buffer overflow attack. It is strongly suggested
that the fgets() function be used in all cases. (See the FSA.)

jaduff46
Jan 28, 2012, 09:13 AM
OP is not at the level where this would matter to him, and I haven't done much C work of late, but thanks for the feedback. I was just trying to help him make some progress.

robvas
Jan 28, 2012, 09:40 AM
OP is not at the level where this would matter to him, and I haven't done much C work of late, but thanks for the feedback. I was just trying to help him make some progress.

Better to stop any bad habits now.

I think the general idea here is that C's stdio is far to cumbersome to use for data input.

subsonix
Jan 28, 2012, 09:41 AM
Then we might as well warn for the use of scanf %s as well since it has the same problem, but unlike gets offers no warnings.

KnightWRX
Jan 28, 2012, 04:37 PM
OP is not at the level where this would matter to him, and I haven't done much C work of late, but thanks for the feedback. I was just trying to help him make some progress.

Actually, the OP is exactly at the level where this would matter to him. You need to break bad habits as early as possible and learning the proper way once is the best way to make sure of that. ;)

timjver
Jan 28, 2012, 07:00 PM
What do you intend the red-hilited code to mean?

When I read it, I see "Declare an array of chars named input, that holds zero chars.".

If an array only holds zero chars, how many characters do you think fgets() or anything else will be able to store there, without overrunning it and damaging other memory locations?

I misunderstood the way to declare arrays. I read a page on arrays in Programming in Objective-C 2.0 and it's clear to me now.

17? Is the input array really that long? I used 17, so the user could insert a maximum of 16 digits. However, when inserting 16 digits having this code:

-(void)setValue
{
Boolean instructionsNeeded = NO;
char input [17];

printf("\nType in a number to check if it's prime.\n");

for ( ; ; )
{
if (instructionsNeeded == YES)
{
printf("Please insert a positive integer, smaller than 2^63.\n");
}

float realInput;

fgets(input, sizeof(input), stdin);

sscanf(input, " %lli", &value);
sscanf(input, " %f", &realInput);

if (value != realInput || value <= 0)
{
printf("Your value is not valid. ");
instructionsNeeded = YES;
continue;
}
break;
}
[self primeTester:value];
}

I get this output:

Type in a number to check if it's prime.
1234567890123456
No, 1234567890123456 is not a prime number, 1234567890123456 = 2^6 x 3 x 7^2 x 301319 x 435503.

Type in a number to check if it's prime.
Your value is not valid. Please insert a positive integer, smaller than 2^63.

I suppose this has to do with the input buffer. When using [20] (2^63 has 19 digits) instead of [17], the same happens, so I used [21]. Inserting 2^63 - 1 now (the input has to be lower than 2^63), it works:

Type in a number to check if it's prime.
9223372036854775807
No, 9223372036854775807 is not a prime number, 9223372036854775807 = 7^2 x 73 x 127 x 337 x 92737 x 649657.

However, when inserting a value between 9223372036854775807 (2^63 - 1) and 9223372586610589696 (which is 0x8000008000000000), the program thinks 2^63 - 1 is inserted:

Type in a number to check if it's prime.
9223372300000000000
No, 9223372036854775807 is not a prime number, 9223372036854775807 = 7^2 x 73 x 127 x 337 x 92737 x 649657.

When inserting a number larger than this, the output is as expected:

Type in a number to check if it's prime.
0x8000008000000001
Your value is not valid. Please insert a positive integer, smaller than 2^63.

Why this limit of 0x8000008000000000 is there, I don't know. Apart from this, for normal values, the program works fine.

However, when inserting a number with more than 20 digits, this happens:

Type in a number to check if it's prime.
123456789012345678901
No, 1234567890 is not a prime number, 1234567890 = 2 x 3^2 x 5 x 3607 x 3803.

Type in a number to check if it's prime.
No, 1 is not a prime number.

This is also because of the input buffer (I suppose), so I somehow had to clear the input buffer. The internet gave me this:

int c;
while (c != '\n' && c != EOF)
{
c = getchar();
}

I put it right after the declaration of realInput. This didn't work, though. The input buffer got somewhat cleared, but not how I wanted it to:

Type in a number to check if it's prime.
23 // nothing happened
85
No, 85 is not a prime number, 85 = 5 x 17.

Other things, like

stdin = 0;

obviously didn't work.

----------

Never, ever use gets(). Ever.

Yeah I read that too, somewhere, thanks though. :)

KnightWRX
Jan 28, 2012, 07:22 PM
Then we might as well warn for the use of scanf %s as well since it has the same problem, but unlike gets offers no warnings.

Actually, it doesn't quite have the same problem as there is a proper way to read in strings with scanf %s, and that is by specifying a size in your format string (%20s for example) or using the GNU "a" flag (dunno where else it is supported) and passing an uninitialized pointer to the function so that scanf does its own initialization (char * string; scanf("%as", &string); ).

gets() is just plain broken, a historical mistake that never should have been and that we're forever stuck with so that older code that relies on it doesn't break (even though all that code is vulnerable to a buffer overflow).

----------

Yeah I read that too, somewhere, thanks though. :)

Good, just wanted to make sure it was pointed out since gets() got proposed. ;)

subsonix
Jan 28, 2012, 09:21 PM
Actually, it doesn't quite have the same problem as there is a proper way to read in strings with scanf %s, and that is by specifying a size in your format string (%20s for example) or using the GNU "a" flag (dunno where else it is supported) and passing an uninitialized pointer to the function so that scanf does its own initialization (char * string; scanf("%as", &string); ).

I did specifically mention scanf %s (without a width) as it was mentioned in the thread along with gets() here. It does have the exact same problem as gets. Adding a width helps that, but on the other hand it does not support variables, constants or use of sizeof() so you are stuck with magic numbers.

Unlike fgets() it does not enforce a size, it also does not include the terminating zero in the length, so for an array of length 20, you have to remember to do: scanf("%19s", string_buffer).

The non-standard GNU extension is not part of stdio.h and C.

KnightWRX
Jan 28, 2012, 10:11 PM
I did specifically mention scanf %s (without a width) as it was mentioned in the thread along with gets() here. It does have the exact same problem as gets. Adding a width helps that, but on the other hand it does not support variables, constants or use of sizeof() so you are stuck with magic numbers.

It does if you build your format string dynamically at runtime. ;)

Again, scanf() isn't as broken as gets(), I don't know why you are trying to argue it is. You can be careful and get good results with scanf(), not so with gets().

My last post on the subject, I don't want to argue around in a circle about it, I've said my piece.

subsonix
Jan 28, 2012, 10:21 PM
Again, scanf() isn't as broken as gets(), I don't know why you are trying to argue it is.

I'm not. I'm arguing that: scanf("%s", buf); is, which is what was mentioned in this thread (post #7). It's use should be discouraged as much as gets().

chown33
Jan 28, 2012, 11:37 PM
However, when inserting a value between 9223372036854775807 (2^63 - 1) and 9223372586610589696 (which is 0x8000008000000000), the program thinks 2^63 - 1 is inserted:

Show the declaration of your value variable. If it's long long, then it will be signed. Think about what 0x8000008000000000 represents in a signed integer (2's complement form).

If you need a hint:
if (value != realInput || value <= 0)

Break this code into two separate if statements. Make the one for value <= 0 say "Your number is negative". Rerun with the problematic input.

This somewhat relates to an earlier question about how you will be able to identify a value that's "too large", if the variable itself can't hold a "too large" value. For example, if the largest representable value is 9999, how can tell when a number greater than that has been entered? The answer lies in checking that the numeral string can be represented at all by the integer variable. You do that by treating it as a series of digits, rather than by converting it to an integer and trying to check for overflow.


As you're finding out, what seems to be a modest and relatively simple part of the problem (error-checking the input) is nowhere near as simple as it first seems. Welcome to real-world programming.

What you'll probably need to do is work with digit strings (strings that contain only digit sequences 0-9) in order to determine grossly out-of-range values. These would be numbers that can't possibly be represented in 64-bit integers, so any attempt at conversion to long long can be rejected even before doing the conversion. For example, a series of 47 digits would be grossly out-of-range. So would 37. As would anything with a decimal point.

Next, work out where the transition is between representable and unrepresentable in 64-bits, then think about how to detect the transition. Here's a hint. If a number is representable, but appending another digit would make it unrepresentable, then the operations that append another digit is:
10 times the current value, plus the value of the new digit 0-9.
So determine the "representable number N" that multiplied by 10 becomes unrepresentable. Now you can write a test (or conversion) that looks for that number, and decide that your number is about to overflow, without actually having it overflow.

All this assumes you really want to perform this amount of input error-checking. You'll learn a lot about real-world programming if you do, but I'm unsure how relevant it is to the exercise in the book. It might be simpler to just reject any number with more than 18 digits. Any number up 18 digits is definitely representable in 64-bits, 19 digits may or may not be, and 20 or more definitely isn't.


This is also because of the input buffer (I suppose), so I somehow had to clear the input buffer. The internet gave me this:

int c;
while (c != '\n' && c != EOF)
{
c = getchar();
}

I put it right after the declaration of realInput. This didn't work, though. The input buffer got somewhat cleared, but not how I wanted it to:

Type in a number to check if it's prime.
23 // nothing happened
85
No, 85 is not a prime number, 85 = 5 x 17.

Go back to the reference doc for fgets(). Read what it has to say about what happens when a newline doesn't occur before the size is reached. Also make sure you understand what fgets() does when a newline DOES occur before the size is reached.

To summarize fgets()'s behavior: If there isn't a newline by the time the size is reached (i.e. the line would be too long to fit), then fgets() won't put a newline there. Conversely, if there is a newline, then there will be a newline character, and it will be right before the terminating NUL. You know its position, because fgets() says it returns when a newline occurs OR the size is reached.

You now have a way to distinguish lines that were too long from lines that will fit.

If and only if the line is too long, should the terminal's input buffer be purged. If the line is NOT too long, i.e. there is a newline in your input array, then DO NOT purge the input buffer. Purging or flushing the input buffer is what your loop is doing. If you do it without a reason, i.e. because the line just input did fit into the input array, then you're discarding the next line of input.

One simple strategy is to define your input array as length 100 chars. Now you'd have to enter a 99-char number (or line of text) before you ran into the problem of the input buffer needing to be purged. You can still write code to do it, and you can also increase the input array's length. Once again, thorough input error-checking is a lot more difficult than it seems.


Other things, like

stdin = 0;

obviously didn't work.

I agree. It's completely wrong. stdin isn't an integer, so assigning it the value 0 does nothing useful, or even reasonable.

lloyddean
Jan 29, 2012, 12:28 AM
The maxumim value of a unsigned long long is 'ULLONG_MAX' which is defined in 'limits.h'


#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

int main(int argc, const char* argv[])
{
char str[256];
sprintf(str, "%llu", ULLONG_MAX);
printf("%s is %lu characters long\n", str, strlen(str));

return EXIT_SUCCESS;
}


outputs: 18446744073709551615 is 20 characters in length!