/commands/c/convert-string-to-int
c convert string to int safely with strtol (not atoi)
- tool
- C
- versions
- C89+ (strtol), C99+ (strtoll)
- os
- any
To convert a string to an integer in C, use strtol(str, &end, 10) from <stdlib.h>: it returns the value as a long, sets end to the first character it did not use, and sets errno to ERANGE when the number is too large. Check all three: end == str means no digits, *end != '\0' means trailing garbage. atoi is shorter but cannot tell "0" from "abc".
A checked conversion
The example uses Clang 20 from the ziglang package on PyPI, run through uv; cc below stands for it. Write gcc or clang on your machine:
cc() { uv run -q --no-project --python 3.13 --with ziglang==0.15.1 python -m ziglang cc -fno-color-diagnostics "$@"; }
cat > parse.c <<'EOF'
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
/* Returns 0 on success and stores the value in *out; -1 on error. */
static int parse_int(const char *s, int *out) {
char *end;
errno = 0;
long v = strtol(s, &end, 10);
if (end == s) return -1; /* no digits at all */
while (*end == ' ' || *end == '\n') end++; /* allow trailing spaces/newline */
if (*end != '\0') return -1; /* trailing garbage */
if (errno == ERANGE || v < INT_MIN || v > INT_MAX) return -1; /* out of range */
*out = (int)v;
return 0;
}
int main(void) {
const char *inputs[] = {"42", " -17", "12abc", "abc", "", "99999999999999999999", "2147483648", " 8 "};
for (size_t i = 0; i < sizeof inputs / sizeof *inputs; i++) {
int n = 0;
int rc = parse_int(inputs[i], &n);
printf("%-24s atoi=%-11d parse_int=%s", inputs[i][0] == '\0' ? "(empty)" : inputs[i],
i == 5 || i == 6 ? 0 : atoi(inputs[i]), rc == 0 ? "ok " : "error");
if (rc == 0) printf(" %d", n);
printf("\n");
}
printf("hex ff = %ld, base 0 \"0x1A\" = %ld\n", strtol("ff", NULL, 16), strtol("0x1A", NULL, 0));
return 0;
}
EOF
cc -std=c17 -Wall -Wextra parse.c -o parse && ./parseOutput
42 atoi=42 parse_int=ok 42 -17 atoi=-17 parse_int=ok -17 12abc atoi=12 parse_int=error abc atoi=0 parse_int=error (empty) atoi=0 parse_int=error 99999999999999999999 atoi=0 parse_int=error 2147483648 atoi=0 parse_int=error 8 atoi=8 parse_int=ok 8 hex ff = 255, base 0 "0x1A" = 26
atoi returns 12 for "12abc" and 0 for "abc" and "" without any sign of an error, while the checked version rejects all three. The two out-of-range inputs are not passed to atoi at all, because its behaviour is undefined for them. Leading white space is skipped by strtol itself, and the loop after it accepts trailing spaces and the newline that fgets leaves at the end of a line.
Functions
| Function | Returns | Errors detected |
|---|---|---|
| strtol(s, &end, base) | long | No digits (end == s), trailing text (*end), overflow (errno = ERANGE) |
| strtoll / strtoul / strtoull | long long / unsigned long / unsigned long long | Same as strtol (C99) |
| atoi(s) / atol(s) | int / long | None; undefined behaviour on overflow |
| sscanf(s, "%d", &n) | Number of fields converted | No digits (returns 0); overflow is undefined behaviour |
| strtol(s, NULL, 0) | long | Base from the prefix: 0x hex, 0 octal, otherwise decimal |
Pitfalls
- Missing header. Calling
strtoloratoiwithout#include <stdlib.h>fails with implicit declaration of function on current compilers. - Leading zeros with base 0.
strtol("08", NULL, 0)reads0as an octal prefix and stops at8; use base 10 for user input. - long vs int. On 64-bit Linux and macOS
longis 64 bits, on 64-bit Windows it is 32 bits: the range check againstINT_MAXkeeps the code portable. - Unsigned parsing.
strtoul("-1", ...)does not fail; it returnsULONG_MAX. Check for a minus sign yourself if negative input must be rejected.
Confirm it worked
Test the cases above (empty, letters, trailing text, a value just past INT_MAX, a newline at the end) and check that each is accepted or rejected as you expect.
FAQ
What is wrong with atoi?
atoi cannot report errors: it returns 0 both for "0" and for "abc", and the behaviour is undefined when the value does not fit in an int. strtol reports where parsing stopped and sets errno to ERANGE on overflow.
Why set errno to 0 before calling strtol?
strtol sets errno only when an error happens; it never resets it. Without errno = 0 first, an old ERANGE from an earlier call would look like a new overflow.
How do I parse into an int instead of a long?
Parse with strtol, then check that the value is between INT_MIN and INT_MAX before casting. On platforms where long is 64 bits, a value can fit in long but not in int.