Reputation: 131
Given the following example parsing a string that only contains valid numeric characters, but is to large for type long int
[0].
char *endptr = NULL;
long int val = strtol("0xfffffffffffffffffffffffff", &endptr, 0);
It is my understanding that the only way to catch this error is to check if errno
is set[1].
Even though errno
is supposed to be thread safe[2], this is not the case in most real-time embedded systems[3] where errno
is a global int
- is this correct? In that case, errno
can not be trusted as it could have been modified from interrupt[4] context.
Is there a way to catch this error without using errno
or is there another workaround? Implementing a custom strto*
do not seems like sane solution, but perhaps there is no other way?
[0] Similar example could be constructed for other functions in strto*
family such as strtod
, strtof
, strtold
, strtoll
, strtoul
and strtoull
.
[1] http://man7.org/linux/man-pages/man3/strtol.3.html
[2] Is errno thread-safe? (also related question)
[3] Example in a "bare metal" project or in a low level RTOS such as Apache mynewt, Zephyr or FreeRTOS.
[4] Modified from interrupt or other context that the OS scheduler might provide. I believe these typically only manages the stack and nothing more.
Upvotes: 2
Views: 712
Reputation: 154280
How to check
strto...()
overflow without errno?
First, I'd doubt OP's assertion: "this is not the case in most real-time embedded systems[2] where errno is a global int - is this correct?"> Be that as it may, here are some ideas.
strto...()
is prone to failure. If your FP supports infinity, simply use isinf()
to detect overflow - even if the input string was "INFINITY" - or test for such infinity strings.Overflow for small values near 0.0 is a rabbit hole of issues for strto...()
. I assume OP is not looking for overflow there.
For integer strto...()
, re-writing is not so hard. Easy to find good source code. If one still wants to avoid that for signed strto...()
, call both as in strtol()
and strtoul()
and compare results. Overflow in the functions return results that do not compare. Works for in long
range negatives like "-123"
too. For unsigned strto...()
- have to think on that.
bool strtol_no_errno(const char *s, long *num) { *num = strtol(s, NULL, 0); unsigned long unum = strtoul(s, NULL, 0); return unum == (unsigned long) *num; }
For integer strto(u)l()
, when long
is narrower than long long
, just call strto(u)ll()
and test if result is in range.
Test
#include <stdbool.h>
#include <stdio.h>
int main( ) {
char buf[100] = "-1";
for (int i = 0; i<21; i++) {
long num;
bool ok = strtol_no_errno(buf + 1, &num);
printf("%30s %d %ld\n", buf + 1, ok, num);
ok = strtol_no_errno(buf + 0, &num);
printf("%30s %d %ld\n", buf, ok, num);
strcat(buf, "0");
}
}
Output
1 1 1
-1 1 -1
10 1 10
-10 1 -10
...
100000000000000000 1 100000000000000000
-100000000000000000 1 -100000000000000000
1000000000000000000 1 1000000000000000000
-1000000000000000000 1 -1000000000000000000
10000000000000000000 0 9223372036854775807
-10000000000000000000 0 -9223372036854775808
100000000000000000000 0 9223372036854775807
-100000000000000000000 0 -9223372036854775808
Upvotes: 1
Reputation: 214265
The first thing you should do is to sanitize the input. For example, if the string length without "0x" is larger than sizeof(long)*2
, then the input is obviously too large. So start by verifying that.
Also make sure that if the input has exactly string length of sizeof(long)*2
, then input[0]
must be '7'
or smaller for signed long
. Or in case input[0]
is '-'
, check input[1]
. (Some 2's complement trickery to consider here, I'll leave that to you.)
With the above sanity checks, it shouldn't be possible to get an overflow. But if you do, the function is guaranteed to return LONG_MAX
(or LONG_MIN
for underflow). Also, endptr
is set to the beginning of the input string in case of such errors. So you can check if the conversion failed like this:
if(val != LONG_MAX)
{
/* OK, normal case */
}
else // val == LONG_MAX
{
if(endptr != input &&
(size_t)(endptr-input) == sizeof(long)*2))
{
/* OK, input was LONG_MAX but no overflow */
}
else
{
/* overflow error */
}
}
This assuming that input
doesn't have the 0x
prefix - if it has, you'll have to tweak the code accordingly.
Same check is needed for underflow.
Upvotes: 0