Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting integral overflow with scanf

When recently answering another question, I discovered a problem with code like:

int n;
scanf ("%d", &n);

With strtol, you can detect overflow because, in that case, the maximum value allowed is inserted into n and errno is set to indicate the overflow, as per C11 7.22.1.4 The strtol, strtoll, strtoul, and strtoull functions /8:

If the correct value is outside the range of representable values, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX, or ULLONG_MAX is returned (according to the return type and sign of the value, if any), and the value of the macro ERANGE is stored in errno.

However, in the sections of the standard dealing with scanf, specifically C11 7.21.6.2 The fscanf function /10, we see:

If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

Now, to me, that means any value can be returned and there's no mention of errno being set to anything. This came to light because the asker of the linked question above was entering 9,999,999,999 into a 32-bit int and getting back 1,410,065,407, a value 233 too small, indicating it had simply wrapped around at the limit of the type.

When I tried it, I got back 2,147,483,647, the largest possible 32-bit unsigned value.

So my question is as follows. How do you detect integral overflow in a portable way when using the scanf family of functions? Is it even possible?

Now I should mention that, on my system (Debian 7), errno is actually set to ERANGE in these circumstances but I can find nothing in the standard that mandates this. Additionally, the return value from scanf is 1, indicating success in scanning the item.

like image 791
paxdiablo Avatar asked Jan 18 '15 06:01

paxdiablo


1 Answers

The only portable way is to specify a field width, e.g. with "%4d" (guaranteed to even fit into a 16-bit int) or by building up the format string at run-time with a field width of (int)(log(INT_MAX) / log(10)). This of course also rejects for example 32000, although it would fit into a 16-bit int. So no, there is no satisfying portable way.

POSIX don't specify more here, nor mention ERANGE.

This manpage mentions setting errno only in case EOF is returned; the glibc documentation doesn't mention ERANGE at all.

That leaves the question what to suggest to beginners for reading integers, where I have no idea. scanf has too many undefined and underspecified aspects to be really useful, fgets cannot be used in productive code because you cannot handle 0-bytes properly, and portable error checking with strtol and friends takes more lines than implementing the functionality yourself (and is quite easy to get wrong). The behaviour of atoi is also undefined for integer overflow.

like image 55
mafso Avatar answered Oct 28 '22 16:10

mafso