Safely parse an integer using the standard C library
Content |
Tested on |
Debian (Etch, Lenny, Squeeze) |
Fedora (14) |
Ubuntu (Hardy, Intrepid, Jaunty, Karmic, Lucid, Maverick, Natty, Precise, Trusty) |
Objective
To convert a string to an integer using the standard C library, giving an error for any input that is not a valid integer
Background
The standard library provides two sets of functions for parsing integers:
- the
atoi
family, which does not report errors, and - the
strtol
family, which can report errors when used with care.
Scenario
Suppose that s
is a pointer to the string to be parsed, which is null-terminated.
Methods
Method (signed integers)
The following code fragment reports as an error any input string that cannot be converted in full:
errno = 0; char* end = s; long int result = strtol(s,&end,10); if (errno == ERANGE) { printf("Error: integer overflow\n"); } else if (*end) { printf("Error: not a valid integer\n"); } else { printf("Result is: %ld",result); }
You can replace strtol
with strtoll
provided you make a corresponding change to the result type. The third argument is the radix, which is decimal in this example but can be changed to any required value in the range 2 to 36. A radix of zero defaults to decimal, but allows octal with a prefix of ‘0’ and hexadecimal with a prefix of ‘0x’.
Setting errno
to zero is necessary because (unlike most standard library functions) strtol
provides no other means to determine whether errno
has been set. A return value of LONG_MIN
or LONG_MAX
is not a reliable indication because it could occur as the result of a valid input.
Method (unsigned integers)
The method for parsing unsigned integers is similar, however there is a complication. Negative values are not rejected by stdtoul
: instead the result is negated, typically causing it to wrap around to a large positive value.
You can prevent this from going undetected by checking whether the first character of the input is a digit. Unfortunately this prevents the conversion function from skipping whitespace, so if you wish to retain that feature then you must implement it explicitly:
while (isspace(*s)) ++s; errno = 0; char* end = s; long int result = strtol(s,&end,10); if (errno == ERANGE) { printf("Error: integer overflow\n"); } else if ((!isdigit(*s)||*s=='+')||*end) { printf("Error: not a valid integer\n"); } else { printf("Result is: %ld",result); }
Variations
Allowing partial conversion
If you are willing to accept a partial conversion then instead of checking that *end
is null you should check whether end
has advanced from its initial value of s
:
if (errno == ERANGE) { printf("Error: integer overflow\n"); } else if (end==s) { printf("Error: not a valid integer\n"); } else { printf("Result is: %ld (converted %u characters)",result,end-s); }